New layout optimization tool. Run layoutopt on the command line.
Change-Id: I8e4697e19ca8a203dc8a41b464f7cb46d52184b0
This commit is contained in:
5
tools/layoutopt/Android.mk
Normal file
5
tools/layoutopt/Android.mk
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copyright 2009 The Android Open Source Project
|
||||
#
|
||||
LAYOUTOPT_LOCAL_DIR := $(call my-dir)
|
||||
include $(LAYOUTOPT_LOCAL_DIR)/libs/Android.mk
|
||||
include $(LAYOUTOPT_LOCAL_DIR)/app/Android.mk
|
||||
0
tools/layoutopt/MODULE_LICENSE_APACHE2
Normal file
0
tools/layoutopt/MODULE_LICENSE_APACHE2
Normal file
5
tools/layoutopt/app/Android.mk
Normal file
5
tools/layoutopt/app/Android.mk
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copyright 2009 The Android Open Source Project
|
||||
#
|
||||
LAYOUTOPT_LOCAL_DIR := $(call my-dir)
|
||||
include $(LAYOUTOPT_LOCAL_DIR)/etc/Android.mk
|
||||
include $(LAYOUTOPT_LOCAL_DIR)/src/Android.mk
|
||||
3
tools/layoutopt/app/README
Normal file
3
tools/layoutopt/app/README
Normal file
@@ -0,0 +1,3 @@
|
||||
Layout optimizer.
|
||||
|
||||
Simple command line front end for the uix library.
|
||||
8
tools/layoutopt/app/etc/Android.mk
Normal file
8
tools/layoutopt/app/etc/Android.mk
Normal file
@@ -0,0 +1,8 @@
|
||||
# Copyright 2009 The Android Open Source Project
|
||||
#
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_PREBUILT_EXECUTABLES := layoutopt
|
||||
include $(BUILD_HOST_PREBUILT)
|
||||
|
||||
63
tools/layoutopt/app/etc/layoutopt
Executable file
63
tools/layoutopt/app/etc/layoutopt
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/bin/sh
|
||||
# Copyright 2009, The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Set up prog to be the path of this script, including following symlinks,
|
||||
# and set up progdir to be the fully-qualified pathname of its directory.
|
||||
prog="$0"
|
||||
while [ -h "${prog}" ]; do
|
||||
newProg=`/bin/ls -ld "${prog}"`
|
||||
newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
|
||||
if expr "x${newProg}" : 'x/' >/dev/null; then
|
||||
prog="${newProg}"
|
||||
else
|
||||
progdir=`dirname "${prog}"`
|
||||
prog="${progdir}/${newProg}"
|
||||
fi
|
||||
done
|
||||
oldwd=`pwd`
|
||||
progdir=`dirname "${prog}"`
|
||||
cd "${progdir}"
|
||||
progdir=`pwd`
|
||||
prog="${progdir}"/`basename "${prog}"`
|
||||
cd "${oldwd}"
|
||||
|
||||
jarfile=layoutopt.jar
|
||||
frameworkdir="$progdir"
|
||||
if [ ! -r "$frameworkdir/$jarfile" ]
|
||||
then
|
||||
frameworkdir=`dirname "$progdir"`/tools/lib
|
||||
libdir=`dirname "$progdir"`/tools/lib
|
||||
fi
|
||||
if [ ! -r "$frameworkdir/$jarfile" ]
|
||||
then
|
||||
frameworkdir=`dirname "$progdir"`/framework
|
||||
libdir=`dirname "$progdir"`/lib
|
||||
fi
|
||||
if [ ! -r "$frameworkdir/$jarfile" ]
|
||||
then
|
||||
echo `basename "$prog"`": can't find $jarfile"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$OSTYPE" = "cygwin" ] ; then
|
||||
jarpath=`cygpath -w "$frameworkdir/$jarfile"`
|
||||
progdir=`cygpath -w "$progdir"`
|
||||
else
|
||||
jarpath="$frameworkdir/$jarfile"
|
||||
fi
|
||||
|
||||
# need to use "java.ext.dirs" because "-jar" causes classpath to be ignored
|
||||
# might need more memory, e.g. -Xmx128M
|
||||
exec java -Djava.ext.dirs="$frameworkdir" -jar "$jarpath" "$@"
|
||||
48
tools/layoutopt/app/etc/layoutopt.bat
Executable file
48
tools/layoutopt/app/etc/layoutopt.bat
Executable file
@@ -0,0 +1,48 @@
|
||||
@echo off
|
||||
rem Copyright (C) 2009 The Android Open Source Project
|
||||
rem
|
||||
rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
rem you may not use this file except in compliance with the License.
|
||||
rem You may obtain a copy of the License at
|
||||
rem
|
||||
rem http://www.apache.org/licenses/LICENSE-2.0
|
||||
rem
|
||||
rem Unless required by applicable law or agreed to in writing, software
|
||||
rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
rem See the License for the specific language governing permissions and
|
||||
rem limitations under the License.
|
||||
|
||||
rem don't modify the caller's environment
|
||||
setlocal
|
||||
|
||||
rem Set up prog to be the path of this script, including following symlinks,
|
||||
rem and set up progdir to be the fully-qualified pathname of its directory.
|
||||
set prog=%~f0
|
||||
|
||||
rem Change current directory and drive to where the script is, to avoid
|
||||
rem issues with directories containing whitespaces.
|
||||
cd /d %~dp0
|
||||
|
||||
set jarfile=layoutopt.jar
|
||||
set frameworkdir=
|
||||
|
||||
if exist %frameworkdir%%jarfile% goto JarFileOk
|
||||
set frameworkdir=lib\
|
||||
|
||||
if exist %frameworkdir%%jarfile% goto JarFileOk
|
||||
set frameworkdir=..\framework\
|
||||
|
||||
:JarFileOk
|
||||
|
||||
if debug NEQ "%1" goto NoDebug
|
||||
set java_debug=-agentlib:jdwp=transport=dt_socket,server=y,address=8050,suspend=y
|
||||
shift 1
|
||||
:NoDebug
|
||||
|
||||
set jarpath=%frameworkdir%%jarfile%
|
||||
|
||||
set javaextdirs=%swt_path%;%frameworkdir%
|
||||
|
||||
call java %java_debug% -Djava.ext.dirs=%javaextdirs% %jarpath% %*
|
||||
|
||||
2
tools/layoutopt/app/etc/manifest.txt
Normal file
2
tools/layoutopt/app/etc/manifest.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Main-Class: com.android.layoutopt.cli.Main
|
||||
Class-Path: groovy-all-1.6.5.jar
|
||||
15
tools/layoutopt/app/src/Android.mk
Normal file
15
tools/layoutopt/app/src/Android.mk
Normal file
@@ -0,0 +1,15 @@
|
||||
# Copyright 2009 The Android Open Source Project
|
||||
#
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||
LOCAL_JAVA_RESOURCE_DIRS := resources
|
||||
|
||||
LOCAL_JAR_MANIFEST := ../etc/manifest.txt
|
||||
LOCAL_JAVA_LIBRARIES := \
|
||||
uix
|
||||
LOCAL_MODULE := layoutopt
|
||||
|
||||
include $(BUILD_HOST_JAVA_LIBRARY)
|
||||
|
||||
123
tools/layoutopt/app/src/com/android/layoutopt/cli/Main.java
Normal file
123
tools/layoutopt/app/src/com/android/layoutopt/cli/Main.java
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.layoutopt.cli;
|
||||
|
||||
import com.android.layoutopt.uix.LayoutAnalyzer;
|
||||
import com.android.layoutopt.uix.LayoutAnalysis;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Command line utility for the uix library.
|
||||
*
|
||||
* This is a simple CLI front-end for the uix library, used to
|
||||
* analyze and optimize Android layout files.
|
||||
*/
|
||||
public class Main {
|
||||
/**
|
||||
* Main entry point of the application.
|
||||
*
|
||||
* @param args One mandatory parameter, a path (absolute or relative)
|
||||
* to an Android XML layout file
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
Parameters p = checkParameters(args);
|
||||
if (!p.valid) {
|
||||
displayHelpMessage();
|
||||
exit();
|
||||
}
|
||||
|
||||
analyzeFiles(p.files);
|
||||
}
|
||||
|
||||
private static void analyzeFiles(File[] files) {
|
||||
LayoutAnalyzer analyzer = new LayoutAnalyzer();
|
||||
for (File file : files) {
|
||||
if (file.isFile()) {
|
||||
analyze(analyzer, file);
|
||||
} else {
|
||||
analyzeFiles(file.listFiles());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void analyze(LayoutAnalyzer analyzer, File file) {
|
||||
LayoutAnalysis analysis = analyzer.analyze(file);
|
||||
System.out.println(analysis.getName());
|
||||
for (LayoutAnalysis.Issue issue : analysis.getIssues()) {
|
||||
System.out.print(String.format("\t%d:%d ", issue.getStartLine(), issue.getEndLine()));
|
||||
System.out.println(issue.getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exits the tool.
|
||||
*/
|
||||
private static void exit() {
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays this tool's help message on the standard output.
|
||||
*/
|
||||
private static void displayHelpMessage() {
|
||||
System.out.println("usage: layoutopt <directories/files to analyze>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a valid Parameters object. Parses the paramters if necessary
|
||||
* and checks for errors.
|
||||
*
|
||||
* @param args The parameters passed from the CLI.
|
||||
*/
|
||||
private static Parameters checkParameters(String[] args) {
|
||||
Parameters p = new Parameters();
|
||||
|
||||
if (args.length < 1) {
|
||||
p.valid = false;
|
||||
} else {
|
||||
List<File> files = new ArrayList<File>();
|
||||
for (String path : args) {
|
||||
File file = new File(path);
|
||||
if (file.exists()) {
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
p.files = files.toArray(new File[files.size()]);
|
||||
p.valid = true;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters parsed from the CLI.
|
||||
*/
|
||||
private static class Parameters {
|
||||
/**
|
||||
* True if this list of parameters is valid, false otherwise.
|
||||
*/
|
||||
boolean valid;
|
||||
|
||||
/**
|
||||
* Paths (absolute or relative) to the files to be analyzed.
|
||||
*/
|
||||
File[] files;
|
||||
}
|
||||
}
|
||||
5
tools/layoutopt/libs/Android.mk
Normal file
5
tools/layoutopt/libs/Android.mk
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copyright 2009 The Android Open Source Project
|
||||
#
|
||||
DDMSLIBS_LOCAL_DIR := $(call my-dir)
|
||||
include $(DDMSLIBS_LOCAL_DIR)/uix/Android.mk
|
||||
|
||||
4
tools/layoutopt/libs/uix/Android.mk
Normal file
4
tools/layoutopt/libs/uix/Android.mk
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copyright 2009 The Android Open Source Project
|
||||
#
|
||||
UIX_LOCAL_DIR := $(call my-dir)
|
||||
include $(UIX_LOCAL_DIR)/src/Android.mk
|
||||
13
tools/layoutopt/libs/uix/src/Android.mk
Normal file
13
tools/layoutopt/libs/uix/src/Android.mk
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright 2009 The Android Open Source Project
|
||||
#
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||
LOCAL_JAVA_RESOURCE_DIRS := resources
|
||||
|
||||
LOCAL_MODULE := uix
|
||||
LOCAL_JAVA_LIBRARIES := \
|
||||
groovy-all-1.6.5
|
||||
|
||||
include $(BUILD_HOST_JAVA_LIBRARY)
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.layoutopt.uix;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Contains the results of a layout analysis. Instances of this class are
|
||||
* generated by {@link com.android.layoutopt.uix.LayoutAnalyzer}.
|
||||
*
|
||||
* @see com.android.layoutopt.uix.LayoutAnalyzer
|
||||
*/
|
||||
public class LayoutAnalysis {
|
||||
/**
|
||||
* Default layout analysis used to describe a problem with the
|
||||
* analysis process.
|
||||
*/
|
||||
static final LayoutAnalysis ERROR = new LayoutAnalysis("");
|
||||
static {
|
||||
ERROR.mAnalyzed = false;
|
||||
ERROR.addIssue("The layout could not be analyzed. Check if you specified a valid "
|
||||
+ "XML layout, if the specified file exists, etc.");
|
||||
}
|
||||
|
||||
private final List<Issue> mIssues = new ArrayList<Issue>();
|
||||
private String mName;
|
||||
private boolean mAnalyzed;
|
||||
|
||||
/**
|
||||
* Creates a new analysis. An analysis is always considered invalid by default.
|
||||
*
|
||||
* @see #validate()
|
||||
* @see #isValid()
|
||||
*/
|
||||
LayoutAnalysis(String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this analysis.
|
||||
*/
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
void setName(String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an issue to the layout analysis.
|
||||
*
|
||||
* @param description Description of the issue.
|
||||
*/
|
||||
public void addIssue(String description) {
|
||||
mIssues.add(new Issue(description));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an issue to the layout analysis.
|
||||
*
|
||||
* @param node The layout node containing the issue.
|
||||
* @param description Description of the issue.
|
||||
*/
|
||||
public void addIssue(LayoutNode node, String description) {
|
||||
mIssues.add(new Issue(node, description));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of issues found during the analysis.
|
||||
*
|
||||
* @return A non-null array of {@link com.android.layoutopt.uix.LayoutAnalysis.Issue}.
|
||||
*/
|
||||
public Issue[] getIssues() {
|
||||
return mIssues.toArray(new Issue[mIssues.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the layout was analyzed. If this method returns false,
|
||||
* a probleme occured during the analysis (missing file, invalid document, etc.)
|
||||
*
|
||||
* @return True if the layout was analyzed, false otherwise.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return mAnalyzed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the analysis. This must be call before this analysis can
|
||||
* be considered valid.
|
||||
*/
|
||||
void validate() {
|
||||
mAnalyzed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an issue discovered during the analysis process.
|
||||
* An issue provides a human-readable description as well as optional solutions.
|
||||
*/
|
||||
public static class Issue {
|
||||
private final String mDescription;
|
||||
private final LayoutNode mNode;
|
||||
|
||||
Issue(String description) {
|
||||
mNode = null;
|
||||
if (description == null) {
|
||||
throw new IllegalArgumentException("The description must be non-null");
|
||||
}
|
||||
mDescription = description;
|
||||
}
|
||||
|
||||
public Issue(LayoutNode node, String description) {
|
||||
mNode = node;
|
||||
if (description == null) {
|
||||
throw new IllegalArgumentException("The description must be non-null");
|
||||
}
|
||||
mDescription = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes this issue to the user.
|
||||
*
|
||||
* @return A String describing the issue, always non-null.
|
||||
*/
|
||||
public String getDescription() {
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the start line of this node.
|
||||
*
|
||||
* @return The start line or -1 if the line is unknown.
|
||||
*/
|
||||
public int getStartLine() {
|
||||
return mNode == null ? -1 : mNode.getStartLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end line of this node.
|
||||
*
|
||||
* @return The end line or -1 if the line is unknown.
|
||||
*/
|
||||
public int getEndLine() {
|
||||
return mNode == null ? -1 : mNode.getEndLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.layoutopt.uix;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.android.layoutopt.uix.xml.XmlDocumentBuilder;
|
||||
import com.android.layoutopt.uix.rules.Rule;
|
||||
import com.android.layoutopt.uix.rules.GroovyRule;
|
||||
import com.android.layoutopt.uix.util.IOUtilities;
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groovy.lang.GroovyShell;
|
||||
import groovy.lang.Script;
|
||||
|
||||
/**
|
||||
* Analysis engine used to discover inefficiencies in Android XML
|
||||
* layout documents.
|
||||
*
|
||||
* Anaylizing an Android XML layout produces a list of explicit messages
|
||||
* as well as possible solutions.
|
||||
*/
|
||||
public class LayoutAnalyzer {
|
||||
private static final String RULES_PREFIX = "rules/";
|
||||
|
||||
private final XmlDocumentBuilder mBuilder = new XmlDocumentBuilder();
|
||||
private final List<Rule> mRules = new ArrayList<Rule>();
|
||||
|
||||
/**
|
||||
* Creates a new layout analyzer. This constructor takes no argument
|
||||
* and will use the default options.
|
||||
*/
|
||||
public LayoutAnalyzer() {
|
||||
loadRules();
|
||||
}
|
||||
|
||||
private void loadRules() {
|
||||
ClassLoader parent = getClass().getClassLoader();
|
||||
GroovyClassLoader loader = new GroovyClassLoader(parent);
|
||||
GroovyShell shell = new GroovyShell(loader);
|
||||
|
||||
URL jar = getClass().getProtectionDomain().getCodeSource().getLocation();
|
||||
ZipFile zip = null;
|
||||
try {
|
||||
zip = new ZipFile(new File(jar.toURI()));
|
||||
Enumeration<? extends ZipEntry> entries = zip.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
if (!entry.isDirectory() && entry.getName().startsWith(RULES_PREFIX)) {
|
||||
loadRule(shell, entry.getName(), zip.getInputStream(entry));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (zip != null) zip.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadRule(GroovyShell shell, String name, InputStream stream) {
|
||||
try {
|
||||
Script script = shell.parse(stream);
|
||||
mRules.add(new GroovyRule(name, script));
|
||||
} catch (Exception e) {
|
||||
System.err.println("Could not load rule " + name + ":");
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
IOUtilities.close(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public void addRule(Rule rule) {
|
||||
if (rule == null) {
|
||||
throw new IllegalArgumentException("A rule must be non-null");
|
||||
}
|
||||
mRules.add(rule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the specified file.
|
||||
*
|
||||
* @param file The file to analyze.
|
||||
*
|
||||
* @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
|
||||
* cannot be null.
|
||||
*/
|
||||
public LayoutAnalysis analyze(File file) {
|
||||
if (file != null && file.exists()) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
return analyze(file.getPath(), in);
|
||||
} catch (FileNotFoundException e) {
|
||||
// Ignore, cannot happen
|
||||
} finally {
|
||||
IOUtilities.close(in);
|
||||
}
|
||||
}
|
||||
|
||||
return LayoutAnalysis.ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the specified XML stream.
|
||||
*
|
||||
* @param stream The stream to analyze.
|
||||
* @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
|
||||
* cannot be null.
|
||||
*/
|
||||
public LayoutAnalysis analyze(InputStream stream) {
|
||||
return analyze("<unknown>", stream);
|
||||
}
|
||||
|
||||
private LayoutAnalysis analyze(String name, InputStream stream) {
|
||||
try {
|
||||
Document document = mBuilder.parse(stream);
|
||||
return analyze(name, document);
|
||||
} catch (SAXException e) {
|
||||
// Ignore
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
return LayoutAnalysis.ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the specified XML document.
|
||||
*
|
||||
* @param content The XML document to analyze.
|
||||
*
|
||||
* @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
|
||||
* cannot be null.
|
||||
*/
|
||||
public LayoutAnalysis analyze(String content) {
|
||||
return analyze("<unknown>", content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the specified XML document.
|
||||
*
|
||||
* @param name The name of the document.
|
||||
* @param content The XML document to analyze.
|
||||
*
|
||||
* @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
|
||||
* cannot be null.
|
||||
*/
|
||||
public LayoutAnalysis analyze(String name, String content) {
|
||||
try {
|
||||
Document document = mBuilder.parse(content);
|
||||
return analyze(name, document);
|
||||
} catch (SAXException e) {
|
||||
// Ignore
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
return LayoutAnalysis.ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the specified XML document.
|
||||
*
|
||||
* @param document The XML document to analyze.
|
||||
*
|
||||
* @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
|
||||
* cannot be null.
|
||||
*/
|
||||
public LayoutAnalysis analyze(Document document) {
|
||||
return analyze("<unknown>", document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the specified XML document.
|
||||
*
|
||||
* @param name The name of the document.
|
||||
* @param document The XML document to analyze.
|
||||
*
|
||||
* @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
|
||||
* cannot be null.
|
||||
*/
|
||||
public LayoutAnalysis analyze(String name, Document document) {
|
||||
LayoutAnalysis analysis = new LayoutAnalysis(name);
|
||||
|
||||
try {
|
||||
Element root = document.getDocumentElement();
|
||||
analyze(analysis, root);
|
||||
} finally {
|
||||
analysis.validate();
|
||||
}
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
private void analyze(LayoutAnalysis analysis, Node node) {
|
||||
NodeList list = node.getChildNodes();
|
||||
int count = list.getLength();
|
||||
|
||||
// Depth first
|
||||
for (int i = 0; i < count; i++) {
|
||||
Node child = list.item(i);
|
||||
if (child.getNodeType() == Node.ELEMENT_NODE) {
|
||||
analyze(analysis, child);
|
||||
}
|
||||
}
|
||||
|
||||
applyRules(analysis, node);
|
||||
}
|
||||
|
||||
private void applyRules(LayoutAnalysis analysis, Node node) {
|
||||
LayoutNode layoutNode = new LayoutNode(node);
|
||||
for (Rule rule : mRules) {
|
||||
rule.run(analysis, layoutNode, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.layoutopt.uix;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Attr;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.android.layoutopt.uix.xml.XmlDocumentBuilder;
|
||||
|
||||
/**
|
||||
* Wrapper class for W3C Node objects. Provides extra utilities specific
|
||||
* to Android XML layouts.
|
||||
*/
|
||||
public class LayoutNode {
|
||||
private static final String ANDROID_LAYOUT_WIDTH = "android:layout_width";
|
||||
private static final String ANDROID_LAYOUT_HEIGHT = "android:layout_height";
|
||||
private static final String VALUE_FILL_PARENT = "fill_parent";
|
||||
private static final String VALUE_WRAP_CONTENT = "wrap_content";
|
||||
|
||||
private Map<String, String> mAttributes;
|
||||
private final Element mNode;
|
||||
private LayoutNode[] mChildren;
|
||||
|
||||
LayoutNode(Node node) {
|
||||
if (node == null) throw new IllegalArgumentException("The node cannot be null");
|
||||
if (node.getNodeType() != Node.ELEMENT_NODE) {
|
||||
throw new IllegalArgumentException("The node must be an element type");
|
||||
}
|
||||
mNode = (Element) node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start line of this node.
|
||||
*
|
||||
* @return The start line or -1 if the line is unknown.
|
||||
*/
|
||||
public int getStartLine() {
|
||||
final Object data = mNode.getUserData(XmlDocumentBuilder.NODE_START_LINE);
|
||||
return data == null ? -1 : (Integer) data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end line of this node.
|
||||
*
|
||||
* @return The end line or -1 if the line is unknown.
|
||||
*/
|
||||
public int getEndLine() {
|
||||
final Object data = mNode.getUserData(XmlDocumentBuilder.NODE_END_LINE);
|
||||
return data == null ? -1 : (Integer) data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped W3C XML node object.
|
||||
*
|
||||
* @return An XML node.
|
||||
*/
|
||||
public Node getNode() {
|
||||
return mNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the node is of the specified type.
|
||||
*
|
||||
* @param name The name of the node.
|
||||
*
|
||||
* @return True if this node has the same name as tagName, false otherwise.
|
||||
*/
|
||||
public boolean is(String name) {
|
||||
return mNode.getNodeName().equals(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the node has declared the specified attribute.
|
||||
*
|
||||
* @param attribute The name of the attribute to check.
|
||||
*
|
||||
* @return True if the attribute is specified, false otherwise.
|
||||
*/
|
||||
public boolean has(String attribute) {
|
||||
return mNode.hasAttribute(attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this node is the document root.
|
||||
*
|
||||
* @return True if the wrapped node is the root of the document,
|
||||
* false otherwise.
|
||||
*/
|
||||
public boolean isRoot() {
|
||||
return mNode == mNode.getOwnerDocument().getDocumentElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this node's width is fill_parent.
|
||||
*/
|
||||
public boolean isWidthFillParent() {
|
||||
return mNode.getAttribute(ANDROID_LAYOUT_WIDTH).equals(VALUE_FILL_PARENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this node's width is wrap_content.
|
||||
*/
|
||||
public boolean isWidthWrapContent() {
|
||||
return mNode.getAttribute(ANDROID_LAYOUT_WIDTH).equals(VALUE_WRAP_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this node's height is fill_parent.
|
||||
*/
|
||||
public boolean isHeightFillParent() {
|
||||
return mNode.getAttribute(ANDROID_LAYOUT_HEIGHT).equals(VALUE_FILL_PARENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this node's height is wrap_content.
|
||||
*/
|
||||
public boolean isHeightWrapContent() {
|
||||
return mNode.getAttribute(ANDROID_LAYOUT_HEIGHT).equals(VALUE_WRAP_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all the attributes declared for this node.
|
||||
*
|
||||
* The name of the attributes contains the namespace.
|
||||
*
|
||||
* @return A map of [name, value] describing the attributes of this node.
|
||||
*/
|
||||
public Map<String, String> getAttributes() {
|
||||
if (mAttributes == null) {
|
||||
NamedNodeMap attributes = mNode.getAttributes();
|
||||
int count = attributes.getLength();
|
||||
mAttributes = new HashMap<String, String>(count);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
Node node = attributes.item(i);
|
||||
Attr attribute = (Attr) node;
|
||||
mAttributes.put(attribute.getName(), attribute.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return mAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the children of this node.
|
||||
*/
|
||||
public LayoutNode[] getChildren() {
|
||||
if (mChildren == null) {
|
||||
NodeList list = mNode.getChildNodes();
|
||||
int count = list.getLength();
|
||||
List<LayoutNode> children = new ArrayList<LayoutNode>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
Node child = list.item(i);
|
||||
if (child.getNodeType() == Node.ELEMENT_NODE) {
|
||||
children.add(new LayoutNode(child));
|
||||
}
|
||||
}
|
||||
mChildren = children.toArray(new LayoutNode[children.size()]);
|
||||
}
|
||||
return mChildren;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.layoutopt.uix.groovy;
|
||||
|
||||
import com.android.layoutopt.uix.LayoutAnalysis;
|
||||
import com.android.layoutopt.uix.LayoutNode;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import groovy.lang.GString;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
/**
|
||||
* Support class for Groovy rules. This class adds new Groovy capabilities
|
||||
* to {@link com.android.layoutopt.uix.LayoutAnalysis} and {@link org.w3c.dom.Node}.
|
||||
*/
|
||||
public class LayoutAnalysisCategory {
|
||||
/**
|
||||
* xmlNode.isRoot()
|
||||
*/
|
||||
public static boolean isRoot(Node node) {
|
||||
return node.getOwnerDocument().getDocumentElement() == node;
|
||||
}
|
||||
|
||||
/**
|
||||
* xmlNode.is("tagName")
|
||||
*/
|
||||
public static boolean is(Node node, String name) {
|
||||
return node.getNodeName().equals(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* xmlNode.depth()
|
||||
*/
|
||||
public static int depth(Node node) {
|
||||
int maxDepth = 0;
|
||||
NodeList list = node.getChildNodes();
|
||||
int count = list.getLength();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
maxDepth = Math.max(maxDepth, depth(list.item(i)));
|
||||
}
|
||||
|
||||
return maxDepth + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* analysis << "The issue"
|
||||
*/
|
||||
public static LayoutAnalysis leftShift(LayoutAnalysis analysis, GString description) {
|
||||
analysis.addIssue(description.toString());
|
||||
return analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* analysis << [node: node, description: "The issue"]
|
||||
*/
|
||||
public static LayoutAnalysis leftShift(LayoutAnalysis analysis, Map issue) {
|
||||
analysis.addIssue((LayoutNode) issue.get("node"), issue.get("description").toString());
|
||||
return analysis;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.layoutopt.uix.rules;
|
||||
|
||||
import groovy.lang.Script;
|
||||
import groovy.lang.Binding;
|
||||
import groovy.lang.Closure;
|
||||
import groovy.xml.dom.DOMCategory;
|
||||
import com.android.layoutopt.uix.LayoutAnalysis;
|
||||
import com.android.layoutopt.uix.LayoutNode;
|
||||
import com.android.layoutopt.uix.groovy.LayoutAnalysisCategory;
|
||||
import org.w3c.dom.Node;
|
||||
import org.codehaus.groovy.runtime.GroovyCategorySupport;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Implementation of a rule using a Groovy script.
|
||||
*/
|
||||
public class GroovyRule implements Rule {
|
||||
private final String mName;
|
||||
private final Script mScript;
|
||||
private final Binding mBinding;
|
||||
private final Closure mClosure;
|
||||
private final List<Class> mCategories;
|
||||
|
||||
public GroovyRule(String name, Script script) {
|
||||
mName = name;
|
||||
mScript = script;
|
||||
mBinding = new Binding();
|
||||
mScript.setBinding(mBinding);
|
||||
mClosure = new Closure(this) {
|
||||
@Override
|
||||
public Object call() {
|
||||
return mScript.run();
|
||||
}
|
||||
};
|
||||
mCategories = new ArrayList<Class>();
|
||||
Collections.addAll(mCategories, DOMCategory.class, LayoutAnalysisCategory.class);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void run(LayoutAnalysis analysis, LayoutNode node, Node xml) {
|
||||
mBinding.setVariable("analysis", analysis);
|
||||
mBinding.setVariable("node", node);
|
||||
mBinding.setVariable("xml", xml);
|
||||
|
||||
GroovyCategorySupport.use(mCategories, mClosure);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.layoutopt.uix.rules;
|
||||
|
||||
import com.android.layoutopt.uix.LayoutAnalysis;
|
||||
import com.android.layoutopt.uix.LayoutNode;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
/**
|
||||
* Interface that define an analysis rule.
|
||||
*/
|
||||
public interface Rule {
|
||||
/**
|
||||
* Returns the name of the rule.
|
||||
*
|
||||
* @return A non-null String.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Runs the rule for the specified node. The rule must add any detected
|
||||
* issue to the analysis.
|
||||
*
|
||||
* @param analysis The resulting analysis.
|
||||
* @param node The layout node to analyse.
|
||||
* @param xml The original XML node.
|
||||
*/
|
||||
void run(LayoutAnalysis analysis, LayoutNode node, Node xml);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.layoutopt.uix.util;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Various utilities related to I/O operations.
|
||||
*/
|
||||
public class IOUtilities {
|
||||
private IOUtilities() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely close a Closeable object, like an InputStream.
|
||||
*
|
||||
* @param stream The object to close.
|
||||
*
|
||||
* @return True if the object is null or was closed properly,
|
||||
* false otherwise.
|
||||
*/
|
||||
public static boolean close(Closeable stream) {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.layoutopt.uix.xml;
|
||||
|
||||
import com.sun.org.apache.xerces.internal.parsers.DOMParser;
|
||||
import com.sun.org.apache.xerces.internal.xni.XMLLocator;
|
||||
import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
|
||||
import com.sun.org.apache.xerces.internal.xni.Augmentations;
|
||||
import com.sun.org.apache.xerces.internal.xni.XNIException;
|
||||
import com.sun.org.apache.xerces.internal.xni.QName;
|
||||
import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.SAXNotRecognizedException;
|
||||
import org.xml.sax.SAXNotSupportedException;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Parses XML documents. This class tries to add meta-data in the resulting DOM
|
||||
* trees to indicate the start and end line numbers of each node.
|
||||
*/
|
||||
public class XmlDocumentBuilder {
|
||||
/**
|
||||
* Name of the node user data containing the start line number of the node.
|
||||
*
|
||||
* @see Node#getUserData(String)
|
||||
*/
|
||||
public static final String NODE_START_LINE = "startLine";
|
||||
|
||||
/**
|
||||
* Name of the node user data containing the end line number of the node.
|
||||
*
|
||||
* @see Node#getUserData(String)
|
||||
*/
|
||||
public static final String NODE_END_LINE = "endLine";
|
||||
|
||||
private final DocumentBuilder mBuilder;
|
||||
private boolean mHasLineNumbersSupport;
|
||||
|
||||
/**
|
||||
* Creates a new XML document builder.
|
||||
*/
|
||||
public XmlDocumentBuilder() {
|
||||
try {
|
||||
Class.forName("com.sun.org.apache.xerces.internal.parsers.DOMParser");
|
||||
mHasLineNumbersSupport = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
if (!mHasLineNumbersSupport) {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
try {
|
||||
mBuilder = factory.newDocumentBuilder();
|
||||
} catch (ParserConfigurationException e) {
|
||||
throw new IllegalStateException("Could not initialize the XML parser");
|
||||
}
|
||||
} else {
|
||||
mBuilder = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the XML documents created by this class are annotated
|
||||
* with line numbers.
|
||||
*
|
||||
* @return True if the parsed documents contain line numbers meta-data,
|
||||
* false otherwise.
|
||||
*
|
||||
* @see #NODE_START_LINE
|
||||
* @see #NODE_END_LINE
|
||||
*/
|
||||
public boolean isHasLineNumbersSupport() {
|
||||
return mHasLineNumbersSupport;
|
||||
}
|
||||
|
||||
public Document parse(InputStream inputStream) throws SAXException, IOException {
|
||||
if (!mHasLineNumbersSupport) {
|
||||
return mBuilder.parse(inputStream);
|
||||
} else {
|
||||
DOMParser parser = new LineNumberDOMParser();
|
||||
parser.parse(new InputSource(inputStream));
|
||||
return parser.getDocument();
|
||||
}
|
||||
}
|
||||
|
||||
public Document parse(String content) throws SAXException, IOException {
|
||||
if (!mHasLineNumbersSupport) {
|
||||
return mBuilder.parse(content);
|
||||
} else {
|
||||
DOMParser parser = new LineNumberDOMParser();
|
||||
parser.parse(content);
|
||||
return parser.getDocument();
|
||||
}
|
||||
}
|
||||
|
||||
public Document parse(File file) throws SAXException, IOException {
|
||||
return parse(new FileInputStream(file));
|
||||
}
|
||||
|
||||
private static class LineNumberDOMParser extends DOMParser {
|
||||
private static final String FEATURE_NODE_EXPANSION =
|
||||
"http://apache.org/xml/features/dom/defer-node-expansion";
|
||||
private static final String CURRENT_NODE =
|
||||
"http://apache.org/xml/properties/dom/current-element-node";
|
||||
|
||||
private XMLLocator mLocator;
|
||||
private LinkedList<Node> mStack = new LinkedList<Node>();
|
||||
|
||||
private LineNumberDOMParser() {
|
||||
try {
|
||||
setFeature(FEATURE_NODE_EXPANSION, false);
|
||||
} catch (SAXNotRecognizedException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SAXNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDocument(XMLLocator xmlLocator, String s,
|
||||
NamespaceContext namespaceContext, Augmentations augmentations)
|
||||
throws XNIException {
|
||||
super.startDocument(xmlLocator, s, namespaceContext, augmentations);
|
||||
|
||||
mLocator = xmlLocator;
|
||||
mStack.add(setNodeLineNumber(NODE_START_LINE));
|
||||
}
|
||||
|
||||
private Node setNodeLineNumber(String tag) {
|
||||
Node node = null;
|
||||
try {
|
||||
node = (Node) getProperty(CURRENT_NODE);
|
||||
} catch (SAXNotRecognizedException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SAXNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (node != null) {
|
||||
node.setUserData(tag, mLocator.getLineNumber(), null);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(QName qName, XMLAttributes xmlAttributes,
|
||||
Augmentations augmentations) throws XNIException {
|
||||
super.startElement(qName, xmlAttributes, augmentations);
|
||||
mStack.add(setNodeLineNumber(NODE_START_LINE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(QName qName, Augmentations augmentations) throws XNIException {
|
||||
super.endElement(qName, augmentations);
|
||||
Node node = mStack.removeLast();
|
||||
if (node != null) {
|
||||
node.setUserData(NODE_END_LINE, mLocator.getLineNumber(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Rule: MergeRootFrameLayout
|
||||
//
|
||||
// Description: Checks whether the root node of the XML document can be
|
||||
// replaced with a <merge /> tag.
|
||||
//
|
||||
// Conditions:
|
||||
// - The node is the root of the document
|
||||
// - The node is a FrameLayout
|
||||
// - The node is fill_parent in both orientation *or* it has no layout_gravity
|
||||
// - The node does not have a background nor a foreground
|
||||
|
||||
if (xml.isRoot() && xml.is("FrameLayout") && !xml.'@android:background' &&
|
||||
!xml.'@android:foreground' && ((node.isWidthFillParent() &&
|
||||
node.isHeightFillParent()) || !xml.'@android:layout_gravity')) {
|
||||
analysis << [node: node, description: "The root-level <FrameLayout/> can be replaced with <merge/>"]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Rule: TooManyLevels
|
||||
//
|
||||
// Description: Checks whether the layout has too many nested groups.
|
||||
//
|
||||
// Conditions:
|
||||
// - The depth of the layout is > 10
|
||||
|
||||
if (xml.isRoot() && (depth = xml.depth()) > 10) {
|
||||
analysis << "This layout has too many nested layouts: ${depth} levels!"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Rule: TooManyViews
|
||||
//
|
||||
// Description: Checks whether the layout has too many views.
|
||||
//
|
||||
// Conditions:
|
||||
// - The document contains more than 80 views
|
||||
|
||||
if (xml.isRoot && (size = xml.'**'.size()) > 80) {
|
||||
analysis << "This layout has too many views: ${size} views!"
|
||||
}
|
||||
7
tools/layoutopt/samples/simple.xml
Normal file
7
tools/layoutopt/samples/simple.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" />
|
||||
85
tools/layoutopt/samples/too_deep.xml
Normal file
85
tools/layoutopt/samples/too_deep.xml
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
413
tools/layoutopt/samples/too_many.xml
Normal file
413
tools/layoutopt/samples/too_many.xml
Normal file
@@ -0,0 +1,413 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ok" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
Reference in New Issue
Block a user