diff options
54 files changed, 4616 insertions, 2260 deletions
diff --git a/androidprefs/src/com/android/prefs/AndroidLocation.java b/androidprefs/src/com/android/prefs/AndroidLocation.java index a7ceb76..68a87b7 100644 --- a/androidprefs/src/com/android/prefs/AndroidLocation.java +++ b/androidprefs/src/com/android/prefs/AndroidLocation.java @@ -32,9 +32,9 @@ public final class AndroidLocation { private static final String ANDROID_SDK_VERSION = "SDK-1.0"; /** - * VM folder inside the path returned by {@link #getFolder()} + * Virtual Device folder inside the path returned by {@link #getFolder()} */ - public static final String FOLDER_VMS = "vm"; + public static final String FOLDER_AVD = "avd"; /** * Throw when the location of the android folder couldn't be found. @@ -56,7 +56,7 @@ public final class AndroidLocation { */ public final static String getFolder() throws AndroidLocationException { if (sPrefsLocation == null) { - String home = findValidPath("user.home", "HOME"); + String home = findValidPath("ANDROID_SDK_HOME", "user.home", "HOME"); // if the above failed, we throw an exception. if (home == null) { diff --git a/apkbuilder/src/com/android/apkbuilder/ApkBuilder.java b/apkbuilder/src/com/android/apkbuilder/ApkBuilder.java index 9a4d24b..8616cda 100644 --- a/apkbuilder/src/com/android/apkbuilder/ApkBuilder.java +++ b/apkbuilder/src/com/android/apkbuilder/ApkBuilder.java @@ -319,7 +319,7 @@ public final class ApkBuilder { * @param files * @param javaResources * @param storeType the optional type of the debug keystore. If <code>null</code>, the default - * keystore type of the VM is used. + * keystore type of the Java VM is used. */ private void createPackage(File outFile, ArrayList<FileInputStream> zipArchives, ArrayList<File> files, ArrayList<ApkFile> javaResources, diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/AdbHelper.java b/ddms/libs/ddmlib/src/com/android/ddmlib/AdbHelper.java index 154bfa1..42022fe 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/AdbHelper.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/AdbHelper.java @@ -268,55 +268,60 @@ final class AdbHelper { }; byte[] reply; - SocketChannel adbChan = SocketChannel.open(adbSockAddr); - adbChan.configureBlocking(false); - - // if the device is not -1, then we first tell adb we're looking to talk - // to a specific device - setDevice(adbChan, device); - - if (write(adbChan, request) == false) - throw new IOException("failed asking for frame buffer"); - - AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); - if (!resp.ioSuccess || !resp.okay) { - Log.w("ddms", "Got timeout or unhappy response from ADB fb req: " - + resp.message); - adbChan.close(); - return null; - } - - reply = new byte[16]; - if (read(adbChan, reply) == false) { - Log.w("ddms", "got partial reply from ADB fb:"); - Log.hexDump("ddms", LogLevel.WARN, reply, 0, reply.length); - adbChan.close(); - return null; - } - ByteBuffer buf = ByteBuffer.wrap(reply); - buf.order(ByteOrder.LITTLE_ENDIAN); - - imageParams.bpp = buf.getInt(); - imageParams.size = buf.getInt(); - imageParams.width = buf.getInt(); - imageParams.height = buf.getInt(); - - Log.d("ddms", "image params: bpp=" + imageParams.bpp + ", size=" - + imageParams.size + ", width=" + imageParams.width - + ", height=" + imageParams.height); - - if (write(adbChan, nudge) == false) - throw new IOException("failed nudging"); - - reply = new byte[imageParams.size]; - if (read(adbChan, reply) == false) { - Log.w("ddms", "got truncated reply from ADB fb data"); - adbChan.close(); - return null; + SocketChannel adbChan = null; + try { + adbChan = SocketChannel.open(adbSockAddr); + adbChan.configureBlocking(false); + + // if the device is not -1, then we first tell adb we're looking to talk + // to a specific device + setDevice(adbChan, device); + + if (write(adbChan, request) == false) + throw new IOException("failed asking for frame buffer"); + + AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); + if (!resp.ioSuccess || !resp.okay) { + Log.w("ddms", "Got timeout or unhappy response from ADB fb req: " + + resp.message); + adbChan.close(); + return null; + } + + reply = new byte[16]; + if (read(adbChan, reply) == false) { + Log.w("ddms", "got partial reply from ADB fb:"); + Log.hexDump("ddms", LogLevel.WARN, reply, 0, reply.length); + adbChan.close(); + return null; + } + ByteBuffer buf = ByteBuffer.wrap(reply); + buf.order(ByteOrder.LITTLE_ENDIAN); + + imageParams.bpp = buf.getInt(); + imageParams.size = buf.getInt(); + imageParams.width = buf.getInt(); + imageParams.height = buf.getInt(); + + Log.d("ddms", "image params: bpp=" + imageParams.bpp + ", size=" + + imageParams.size + ", width=" + imageParams.width + + ", height=" + imageParams.height); + + if (write(adbChan, nudge) == false) + throw new IOException("failed nudging"); + + reply = new byte[imageParams.size]; + if (read(adbChan, reply) == false) { + Log.w("ddms", "got truncated reply from ADB fb data"); + adbChan.close(); + return null; + } + imageParams.data = reply; + } finally { + if (adbChan != null) { + adbChan.close(); + } } - imageParams.data = reply; - - adbChan.close(); return imageParams; } @@ -330,58 +335,61 @@ final class AdbHelper { throws IOException { Log.v("ddms", "execute: running " + command); - SocketChannel adbChan = SocketChannel.open(adbSockAddr); - adbChan.configureBlocking(false); - - // if the device is not -1, then we first tell adb we're looking to talk - // to a specific device - setDevice(adbChan, device); - - byte[] request = formAdbRequest("shell:" + command); //$NON-NLS-1$ - if (write(adbChan, request) == false) - throw new IOException("failed submitting shell command"); + SocketChannel adbChan = null; + try { + adbChan = SocketChannel.open(adbSockAddr); + adbChan.configureBlocking(false); - AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); - if (!resp.ioSuccess || !resp.okay) { - Log.e("ddms", "ADB rejected shell command (" + command + "): " - + resp.message); - throw new IOException("sad result from adb: " + resp.message); - } + // if the device is not -1, then we first tell adb we're looking to + // talk + // to a specific device + setDevice(adbChan, device); - byte[] data = new byte[16384]; - ByteBuffer buf = ByteBuffer.wrap(data); - while (true) { - int count; + byte[] request = formAdbRequest("shell:" + command); //$NON-NLS-1$ + if (write(adbChan, request) == false) + throw new IOException("failed submitting shell command"); - if (rcvr != null && rcvr.isCancelled()) { - Log.v("ddms", "execute: cancelled"); - break; + AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); + if (!resp.ioSuccess || !resp.okay) { + Log.e("ddms", "ADB rejected shell command (" + command + "): " + resp.message); + throw new IOException("sad result from adb: " + resp.message); } - count = adbChan.read(buf); - if (count < 0) { - // we're at the end, we flush the output - rcvr.flush(); - Log.v("ddms", - "execute '" + command + "' on '" + device + "' : EOF hit. Read: " + count); - break; - } else if (count == 0) { - try { - Thread.sleep(WAIT_TIME * 5); - } catch (InterruptedException ie) { + byte[] data = new byte[16384]; + ByteBuffer buf = ByteBuffer.wrap(data); + while (true) { + int count; + + if (rcvr != null && rcvr.isCancelled()) { + Log.v("ddms", "execute: cancelled"); + break; } - } else { - if (rcvr != null) { - rcvr.addOutput(buf.array(), buf.arrayOffset(), buf - .position()); + + count = adbChan.read(buf); + if (count < 0) { + // we're at the end, we flush the output + rcvr.flush(); + Log.v("ddms", "execute '" + command + "' on '" + device + "' : EOF hit. Read: " + + count); + break; + } else if (count == 0) { + try { + Thread.sleep(WAIT_TIME * 5); + } catch (InterruptedException ie) { + } + } else { + if (rcvr != null) { + rcvr.addOutput(buf.array(), buf.arrayOffset(), buf.position()); + } + buf.rewind(); } - buf.rewind(); } + } finally { + if (adbChan != null) { + adbChan.close(); + } + Log.v("ddms", "execute: returning"); } - - adbChan.close(); - - Log.v("ddms", "execute: returning"); } /** @@ -407,49 +415,55 @@ final class AdbHelper { */ public static void runLogService(InetSocketAddress adbSockAddr, Device device, String logName, LogReceiver rcvr) throws IOException { - SocketChannel adbChan = SocketChannel.open(adbSockAddr); - adbChan.configureBlocking(false); - - // if the device is not -1, then we first tell adb we're looking to talk - // to a specific device - setDevice(adbChan, device); - - byte[] request = formAdbRequest("log:" + logName); - if (write(adbChan, request) == false) { - throw new IOException("failed to submit the log command"); - } - - AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); - if (!resp.ioSuccess || !resp.okay) { - throw new IOException("Device rejected log command: " + resp.message); - } - - byte[] data = new byte[16384]; - ByteBuffer buf = ByteBuffer.wrap(data); - while (true) { - int count; - - if (rcvr != null && rcvr.isCancelled()) { - break; + SocketChannel adbChan = null; + + try { + adbChan = SocketChannel.open(adbSockAddr); + adbChan.configureBlocking(false); + + // if the device is not -1, then we first tell adb we're looking to talk + // to a specific device + setDevice(adbChan, device); + + byte[] request = formAdbRequest("log:" + logName); + if (write(adbChan, request) == false) { + throw new IOException("failed to submit the log command"); } - - count = adbChan.read(buf); - if (count < 0) { - break; - } else if (count == 0) { - try { - Thread.sleep(WAIT_TIME * 5); - } catch (InterruptedException ie) { + + AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); + if (!resp.ioSuccess || !resp.okay) { + throw new IOException("Device rejected log command: " + resp.message); + } + + byte[] data = new byte[16384]; + ByteBuffer buf = ByteBuffer.wrap(data); + while (true) { + int count; + + if (rcvr != null && rcvr.isCancelled()) { + break; } - } else { - if (rcvr != null) { - rcvr.parseNewData(buf.array(), buf.arrayOffset(), buf.position()); + + count = adbChan.read(buf); + if (count < 0) { + break; + } else if (count == 0) { + try { + Thread.sleep(WAIT_TIME * 5); + } catch (InterruptedException ie) { + } + } else { + if (rcvr != null) { + rcvr.parseNewData(buf.array(), buf.arrayOffset(), buf.position()); + } + buf.rewind(); } - buf.rewind(); + } + } finally { + if (adbChan != null) { + adbChan.close(); } } - - adbChan.close(); } /** @@ -464,24 +478,29 @@ final class AdbHelper { public static boolean createForward(InetSocketAddress adbSockAddr, Device device, int localPort, int remotePort) throws IOException { - SocketChannel adbChan = SocketChannel.open(adbSockAddr); - adbChan.configureBlocking(false); - - byte[] request = formAdbRequest(String.format( - "host-serial:%1$s:forward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$ - device.serialNumber, localPort, remotePort)); - - if (write(adbChan, request) == false) { - throw new IOException("failed to submit the forward command."); - } - - AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); - if (!resp.ioSuccess || !resp.okay) { - throw new IOException("Device rejected command: " + resp.message); + SocketChannel adbChan = null; + try { + adbChan = SocketChannel.open(adbSockAddr); + adbChan.configureBlocking(false); + + byte[] request = formAdbRequest(String.format( + "host-serial:%1$s:forward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$ + device.serialNumber, localPort, remotePort)); + + if (write(adbChan, request) == false) { + throw new IOException("failed to submit the forward command."); + } + + AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); + if (!resp.ioSuccess || !resp.okay) { + throw new IOException("Device rejected command: " + resp.message); + } + } finally { + if (adbChan != null) { + adbChan.close(); + } } - adbChan.close(); - return true; } @@ -497,24 +516,29 @@ final class AdbHelper { public static boolean removeForward(InetSocketAddress adbSockAddr, Device device, int localPort, int remotePort) throws IOException { - SocketChannel adbChan = SocketChannel.open(adbSockAddr); - adbChan.configureBlocking(false); - - byte[] request = formAdbRequest(String.format( - "host-serial:%1$s:killforward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$ - device.serialNumber, localPort, remotePort)); - - if (!write(adbChan, request)) { - throw new IOException("failed to submit the remove forward command."); - } - - AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); - if (!resp.ioSuccess || !resp.okay) { - throw new IOException("Device rejected command: " + resp.message); + SocketChannel adbChan = null; + try { + adbChan = SocketChannel.open(adbSockAddr); + adbChan.configureBlocking(false); + + byte[] request = formAdbRequest(String.format( + "host-serial:%1$s:killforward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$ + device.serialNumber, localPort, remotePort)); + + if (!write(adbChan, request)) { + throw new IOException("failed to submit the remove forward command."); + } + + AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); + if (!resp.ioSuccess || !resp.okay) { + throw new IOException("Device rejected command: " + resp.message); + } + } finally { + if (adbChan != null) { + adbChan.close(); + } } - adbChan.close(); - return true; } diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java b/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java index 8291f59..34ef432 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java @@ -69,8 +69,8 @@ public final class Device implements IDevice { /** Serial number of the device */ String serialNumber = null; - /** Name of the vm */ - String mVmName = null; + /** Name of the AVD */ + String mAvdName = null; /** State of the device. */ DeviceState state = null; @@ -94,8 +94,8 @@ public final class Device implements IDevice { return serialNumber; } - public String getVmName() { - return mVmName; + public String getAvdName() { + return mAvdName; } diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java b/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java index 8547ac1..f9d0fa0 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java @@ -420,11 +420,11 @@ final class DeviceMonitor { device.executeShellCommand(GetPropReceiver.GETPROP_COMMAND, new GetPropReceiver(device)); - // now get the emulator VM name (if applicable). + // now get the emulator Virtual Device name (if applicable). if (device.isEmulator()) { EmulatorConsole console = EmulatorConsole.getConsole(device); if (console != null) { - device.mVmName = console.getVmName(); + device.mAvdName = console.getAvdName(); } } } catch (IOException e) { @@ -470,7 +470,7 @@ final class DeviceMonitor { } catch (IOException e1) { // we can ignore that one. It may already have been closed. } - Log.e("DeviceMonitor", + Log.d("DeviceMonitor", "Connection Failure when starting to monitor device '" + device + "' : " + e.getMessage()); } @@ -558,7 +558,7 @@ final class DeviceMonitor { processIncomingJdwpData(device, socket, length); } catch (IOException ioe) { - Log.e("DeviceMonitor", + Log.d("DeviceMonitor", "Error reading jdwp list: " + ioe.getMessage()); socket.close(); diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/EmulatorConsole.java b/ddms/libs/ddmlib/src/com/android/ddmlib/EmulatorConsole.java index e00073c..74c432d 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/EmulatorConsole.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/EmulatorConsole.java @@ -54,7 +54,7 @@ public final class EmulatorConsole { private final static String HOST = "127.0.0.1"; //$NON-NLS-1$ private final static String COMMAND_PING = "help\r\n"; //$NON-NLS-1$ - private final static String COMMAND_VM_NAME = "vm name\r\n"; //$NON-NLS-1$ + private final static String COMMAND_AVD_NAME = "vm name\r\n"; //$NON-NLS-1$ // TODO change with emulator private final static String COMMAND_KILL = "kill\r\n"; //$NON-NLS-1$ private final static String COMMAND_GSM_STATUS = "gsm status\r\n"; //$NON-NLS-1$ private final static String COMMAND_GSM_CALL = "gsm call %1$s\r\n"; //$NON-NLS-1$ @@ -309,8 +309,8 @@ public final class EmulatorConsole { } } - public synchronized String getVmName() { - if (sendCommand(COMMAND_VM_NAME)) { + public synchronized String getAvdName() { + if (sendCommand(COMMAND_AVD_NAME)) { String[] result = readLines(); if (result != null && result.length == 2) { // this should be the name on first line, // and ok on 2nd line diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java b/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java index 61d1ca4..664b0c9 100755 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java @@ -46,13 +46,13 @@ public interface IDevice { public String getSerialNumber(); /** - * Returns the name of the VM the emulator is running. + * Returns the name of the AVD the emulator is running. * <p/>This is only valid if {@link #isEmulator()} returns true. - * <p/>If the emulator is not running any VM (for instance it's running from an Android source + * <p/>If the emulator is not running any AVD (for instance it's running from an Android source * tree build), this method will return "<code><build></code>". - * @return the name of the VM or <code>null</code> if there isn't any. + * @return the name of the AVD or <code>null</code> if there isn't any. */ - public String getVmName(); + public String getAvdName(); /** * Returns the state of the device. diff --git a/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java b/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java index 556fc9b..54ffae8 100644 --- a/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java +++ b/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java @@ -201,7 +201,7 @@ public class RemoteAndroidTestRunnerTest extends TestCase { throw new UnsupportedOperationException(); } - public String getVmName() { + public String getAvdName() { return ""; } diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java index 1331a09..fa3f0e4 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/DevicePanel.java @@ -200,15 +200,15 @@ public final class DevicePanel extends Panel implements IDebugBridgeChangeListen case DEVICE_COL_STATE: return getStateString(device); case DEVICE_COL_BUILD: { - String vmName = device.getVmName(); + String avdName = device.getAvdName(); String debuggable = device.getProperty(Device.PROP_DEBUGGABLE); String version = device.getProperty(Device.PROP_BUILD_VERSION); if (device.isEmulator()) { if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$ - return String.format("%1$s [%2$s, debug]", vmName, //$NON-NLS-1$ + return String.format("%1$s [%2$s, debug]", avdName, //$NON-NLS-1$ version); } else { - return String.format("%1$s [%2$s]", vmName, version); //$NON-NLS-1$ + return String.format("%1$s [%2$s]", avdName, version); //$NON-NLS-1$ } } else { if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$ diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayFilteredLog.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayFilteredLog.java new file mode 100644 index 0000000..473387a --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayFilteredLog.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2008 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.ddmuilib.log.event; + +import com.android.ddmlib.log.EventContainer; +import com.android.ddmlib.log.EventLogParser; + +import java.util.ArrayList; + +public class DisplayFilteredLog extends DisplayLog { + + public DisplayFilteredLog(String name) { + super(name); + } + + /** + * Adds event to the display. + */ + @Override + void newEvent(EventContainer event, EventLogParser logParser) { + ArrayList<ValueDisplayDescriptor> valueDescriptors = + new ArrayList<ValueDisplayDescriptor>(); + + ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors = + new ArrayList<OccurrenceDisplayDescriptor>(); + + if (filterEvent(event, valueDescriptors, occurrenceDescriptors)) { + addToLog(event, logParser, valueDescriptors, occurrenceDescriptors); + } + } + + /** + * Gets display type + * + * @return display type as an integer + */ + @Override + int getDisplayType() { + return DISPLAY_TYPE_FILTERED_LOG; + } +} diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayGraph.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayGraph.java new file mode 100644 index 0000000..0cffd7e --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayGraph.java @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2008 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.ddmuilib.log.event; + +import com.android.ddmlib.log.EventContainer; +import com.android.ddmlib.log.EventLogParser; +import com.android.ddmlib.log.EventValueDescription; +import com.android.ddmlib.log.InvalidTypeException; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.AbstractXYItemRenderer; +import org.jfree.chart.renderer.xy.XYAreaRenderer; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.data.time.Millisecond; +import org.jfree.data.time.TimeSeries; +import org.jfree.data.time.TimeSeriesCollection; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class DisplayGraph extends EventDisplay { + + public DisplayGraph(String name) { + super(name); + } + + /** + * Resets the display. + */ + @Override + void resetUI() { + Collection<TimeSeriesCollection> datasets = mValueTypeDataSetMap.values(); + for (TimeSeriesCollection dataset : datasets) { + dataset.removeAllSeries(); + } + if (mOccurrenceDataSet != null) { + mOccurrenceDataSet.removeAllSeries(); + } + mValueDescriptorSeriesMap.clear(); + mOcurrenceDescriptorSeriesMap.clear(); + } + + /** + * Creates the UI for the event display. + * @param parent the parent composite. + * @param logParser the current log parser. + * @return the created control (which may have children). + */ + @Override + public Control createComposite(final Composite parent, EventLogParser logParser, + final ILogColumnListener listener) { + String title = getChartTitle(logParser); + return createCompositeChart(parent, logParser, title); + } + + /** + * Adds event to the display. + */ + @Override + void newEvent(EventContainer event, EventLogParser logParser) { + ArrayList<ValueDisplayDescriptor> valueDescriptors = + new ArrayList<ValueDisplayDescriptor>(); + + ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors = + new ArrayList<OccurrenceDisplayDescriptor>(); + + if (filterEvent(event, valueDescriptors, occurrenceDescriptors)) { + updateChart(event, logParser, valueDescriptors, occurrenceDescriptors); + } + } + + /** + * Updates the chart with the {@link EventContainer} by adding the values/occurrences defined + * by the {@link ValueDisplayDescriptor} and {@link OccurrenceDisplayDescriptor} objects from + * the two lists. + * <p/>This method is only called when at least one of the descriptor list is non empty. + * @param event + * @param logParser + * @param valueDescriptors + * @param occurrenceDescriptors + */ + private void updateChart(EventContainer event, EventLogParser logParser, + ArrayList<ValueDisplayDescriptor> valueDescriptors, + ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) { + Map<Integer, String> tagMap = logParser.getTagMap(); + + Millisecond millisecondTime = null; + long msec = -1; + + // If the event container is a cpu container (tag == 2721), and there is no descriptor + // for the total CPU load, then we do accumulate all the values. + boolean accumulateValues = false; + double accumulatedValue = 0; + + if (event.mTag == 2721) { + accumulateValues = true; + for (ValueDisplayDescriptor descriptor : valueDescriptors) { + accumulateValues &= (descriptor.valueIndex != 0); + } + } + + for (ValueDisplayDescriptor descriptor : valueDescriptors) { + try { + // get the hashmap for this descriptor + HashMap<Integer, TimeSeries> map = mValueDescriptorSeriesMap.get(descriptor); + + // if it's not there yet, we create it. + if (map == null) { + map = new HashMap<Integer, TimeSeries>(); + mValueDescriptorSeriesMap.put(descriptor, map); + } + + // get the TimeSeries for this pid + TimeSeries timeSeries = map.get(event.pid); + + // if it doesn't exist yet, we create it + if (timeSeries == null) { + // get the series name + String seriesFullName = null; + String seriesLabel = getSeriesLabel(event, descriptor); + + switch (mValueDescriptorCheck) { + case EVENT_CHECK_SAME_TAG: + seriesFullName = String.format("%1$s / %2$s", seriesLabel, + descriptor.valueName); + break; + case EVENT_CHECK_SAME_VALUE: + seriesFullName = String.format("%1$s", seriesLabel); + break; + default: + seriesFullName = String.format("%1$s / %2$s: %3$s", seriesLabel, + tagMap.get(descriptor.eventTag), + descriptor.valueName); + break; + } + + // get the data set for this ValueType + TimeSeriesCollection dataset = getValueDataset( + logParser.getEventInfoMap().get(event.mTag)[descriptor.valueIndex] + .getValueType(), + accumulateValues); + + // create the series + timeSeries = new TimeSeries(seriesFullName, Millisecond.class); + if (mMaximumChartItemAge != -1) { + timeSeries.setMaximumItemAge(mMaximumChartItemAge * 1000); + } + + dataset.addSeries(timeSeries); + + // add it to the map. + map.put(event.pid, timeSeries); + } + + // update the timeSeries. + + // get the value from the event + double value = event.getValueAsDouble(descriptor.valueIndex); + + // accumulate the values if needed. + if (accumulateValues) { + accumulatedValue += value; + value = accumulatedValue; + } + + // get the time + if (millisecondTime == null) { + msec = (long)event.sec * 1000L + (event.nsec / 1000000L); + millisecondTime = new Millisecond(new Date(msec)); + } + + // add the value to the time series + timeSeries.addOrUpdate(millisecondTime, value); + } catch (InvalidTypeException e) { + // just ignore this descriptor if there's a type mismatch + } + } + + for (OccurrenceDisplayDescriptor descriptor : occurrenceDescriptors) { + try { + // get the hashmap for this descriptor + HashMap<Integer, TimeSeries> map = mOcurrenceDescriptorSeriesMap.get(descriptor); + + // if it's not there yet, we create it. + if (map == null) { + map = new HashMap<Integer, TimeSeries>(); + mOcurrenceDescriptorSeriesMap.put(descriptor, map); + } + + // get the TimeSeries for this pid + TimeSeries timeSeries = map.get(event.pid); + + // if it doesn't exist yet, we create it. + if (timeSeries == null) { + String seriesLabel = getSeriesLabel(event, descriptor); + + String seriesFullName = String.format("[%1$s:%2$s]", + tagMap.get(descriptor.eventTag), seriesLabel); + + timeSeries = new TimeSeries(seriesFullName, Millisecond.class); + if (mMaximumChartItemAge != -1) { + timeSeries.setMaximumItemAge(mMaximumChartItemAge); + } + + getOccurrenceDataSet().addSeries(timeSeries); + + map.put(event.pid, timeSeries); + } + + // update the series + + // get the time + if (millisecondTime == null) { + msec = (long)event.sec * 1000L + (event.nsec / 1000000L); + millisecondTime = new Millisecond(new Date(msec)); + } + + // add the value to the time series + timeSeries.addOrUpdate(millisecondTime, 0); // the value is unused + } catch (InvalidTypeException e) { + // just ignore this descriptor if there's a type mismatch + } + } + + // go through all the series and remove old values. + if (msec != -1 && mMaximumChartItemAge != -1) { + Collection<HashMap<Integer, TimeSeries>> pidMapValues = + mValueDescriptorSeriesMap.values(); + + for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) { + Collection<TimeSeries> seriesCollection = pidMapValue.values(); + + for (TimeSeries timeSeries : seriesCollection) { + timeSeries.removeAgedItems(msec, true); + } + } + + pidMapValues = mOcurrenceDescriptorSeriesMap.values(); + for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) { + Collection<TimeSeries> seriesCollection = pidMapValue.values(); + + for (TimeSeries timeSeries : seriesCollection) { + timeSeries.removeAgedItems(msec, true); + } + } + } + } + + /** + * Returns a {@link TimeSeriesCollection} for a specific {@link com.android.ddmlib.log.EventValueDescription.ValueType}. + * If the data set is not yet created, it is first allocated and set up into the + * {@link org.jfree.chart.JFreeChart} object. + * @param type the {@link com.android.ddmlib.log.EventValueDescription.ValueType} of the data set. + * @param accumulateValues + */ + private TimeSeriesCollection getValueDataset(EventValueDescription.ValueType type, boolean accumulateValues) { + TimeSeriesCollection dataset = mValueTypeDataSetMap.get(type); + if (dataset == null) { + // create the data set and store it in the map + dataset = new TimeSeriesCollection(); + mValueTypeDataSetMap.put(type, dataset); + + // create the renderer and configure it depending on the ValueType + AbstractXYItemRenderer renderer; + if (type == EventValueDescription.ValueType.PERCENT && accumulateValues) { + renderer = new XYAreaRenderer(); + } else { + XYLineAndShapeRenderer r = new XYLineAndShapeRenderer(); + r.setBaseShapesVisible(type != EventValueDescription.ValueType.PERCENT); + + renderer = r; + } + + // set both the dataset and the renderer in the plot object. + XYPlot xyPlot = mChart.getXYPlot(); + xyPlot.setDataset(mDataSetCount, dataset); + xyPlot.setRenderer(mDataSetCount, renderer); + + // put a new axis label, and configure it. + NumberAxis axis = new NumberAxis(type.toString()); + + if (type == EventValueDescription.ValueType.PERCENT) { + // force percent range to be (0,100) fixed. + axis.setAutoRange(false); + axis.setRange(0., 100.); + } + + // for the index, we ignore the occurrence dataset + int count = mDataSetCount; + if (mOccurrenceDataSet != null) { + count--; + } + + xyPlot.setRangeAxis(count, axis); + if ((count % 2) == 0) { + xyPlot.setRangeAxisLocation(count, AxisLocation.BOTTOM_OR_LEFT); + } else { + xyPlot.setRangeAxisLocation(count, AxisLocation.TOP_OR_RIGHT); + } + + // now we link the dataset and the axis + xyPlot.mapDatasetToRangeAxis(mDataSetCount, count); + + mDataSetCount++; + } + + return dataset; + } + + /** + * Return the series label for this event. This only contains the pid information. + * @param event the {@link EventContainer} + * @param descriptor the {@link OccurrenceDisplayDescriptor} + * @return the series label. + * @throws InvalidTypeException + */ + private String getSeriesLabel(EventContainer event, OccurrenceDisplayDescriptor descriptor) + throws InvalidTypeException { + if (descriptor.seriesValueIndex != -1) { + if (descriptor.includePid == false) { + return event.getValueAsString(descriptor.seriesValueIndex); + } else { + return String.format("%1$s (%2$d)", + event.getValueAsString(descriptor.seriesValueIndex), event.pid); + } + } + + return Integer.toString(event.pid); + } + + /** + * Returns the {@link TimeSeriesCollection} for the occurrence display. If the data set is not + * yet created, it is first allocated and set up into the {@link org.jfree.chart.JFreeChart} object. + */ + private TimeSeriesCollection getOccurrenceDataSet() { + if (mOccurrenceDataSet == null) { + mOccurrenceDataSet = new TimeSeriesCollection(); + + XYPlot xyPlot = mChart.getXYPlot(); + xyPlot.setDataset(mDataSetCount, mOccurrenceDataSet); + + OccurrenceRenderer renderer = new OccurrenceRenderer(); + renderer.setBaseShapesVisible(false); + xyPlot.setRenderer(mDataSetCount, renderer); + + mDataSetCount++; + } + + return mOccurrenceDataSet; + } + + /** + * Gets display type + * + * @return display type as an integer + */ + @Override + int getDisplayType() { + return DISPLAY_TYPE_GRAPH; + } + + /** + * Sets the current {@link EventLogParser} object. + */ + @Override + protected void setNewLogParser(EventLogParser logParser) { + if (mChart != null) { + mChart.setTitle(getChartTitle(logParser)); + } + } + /** + * Returns a meaningful chart title based on the value of {@link #mValueDescriptorCheck}. + * + * @param logParser the logParser. + * @return the chart title. + */ + private String getChartTitle(EventLogParser logParser) { + if (mValueDescriptors.size() > 0) { + String chartDesc = null; + switch (mValueDescriptorCheck) { + case EVENT_CHECK_SAME_TAG: + if (logParser != null) { + chartDesc = logParser.getTagMap().get(mValueDescriptors.get(0).eventTag); + } + break; + case EVENT_CHECK_SAME_VALUE: + if (logParser != null) { + chartDesc = String.format("%1$s / %2$s", + logParser.getTagMap().get(mValueDescriptors.get(0).eventTag), + mValueDescriptors.get(0).valueName); + } + break; + } + + if (chartDesc != null) { + return String.format("%1$s - %2$s", mName, chartDesc); + } + } + + return mName; + } +}
\ No newline at end of file diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayLog.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayLog.java new file mode 100644 index 0000000..26296f3 --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplayLog.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2008 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.ddmuilib.log.event; + +import com.android.ddmlib.log.EventContainer; +import com.android.ddmlib.log.EventLogParser; +import com.android.ddmlib.log.EventValueDescription; +import com.android.ddmlib.log.InvalidTypeException; +import com.android.ddmuilib.DdmUiPreferences; +import com.android.ddmuilib.TableHelper; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.ScrollBar; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; + +import java.util.ArrayList; +import java.util.Calendar; + +public class DisplayLog extends EventDisplay { + public DisplayLog(String name) { + super(name); + } + + private final static String PREFS_COL_DATE = "EventLogPanel.log.Col1"; //$NON-NLS-1$ + private final static String PREFS_COL_PID = "EventLogPanel.log.Col2"; //$NON-NLS-1$ + private final static String PREFS_COL_EVENTTAG = "EventLogPanel.log.Col3"; //$NON-NLS-1$ + private final static String PREFS_COL_VALUENAME = "EventLogPanel.log.Col4"; //$NON-NLS-1$ + private final static String PREFS_COL_VALUE = "EventLogPanel.log.Col5"; //$NON-NLS-1$ + private final static String PREFS_COL_TYPE = "EventLogPanel.log.Col6"; //$NON-NLS-1$ + + /** + * Resets the display. + */ + @Override + void resetUI() { + mLogTable.removeAll(); + } + + /** + * Adds event to the display. + */ + @Override + void newEvent(EventContainer event, EventLogParser logParser) { + addToLog(event, logParser); + } + + /** + * Creates the UI for the event display. + * + * @param parent the parent composite. + * @param logParser the current log parser. + * @return the created control (which may have children). + */ + @Override + Control createComposite(Composite parent, EventLogParser logParser, ILogColumnListener listener) { + return createLogUI(parent, listener); + } + + /** + * Adds an {@link EventContainer} to the log. + * + * @param event the event. + * @param logParser the log parser. + */ + private void addToLog(EventContainer event, EventLogParser logParser) { + ScrollBar bar = mLogTable.getVerticalBar(); + boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb(); + + // get the date. + Calendar c = Calendar.getInstance(); + long msec = (long) event.sec * 1000L; + c.setTimeInMillis(msec); + + // convert the time into a string + String date = String.format("%1$tF %1$tT", c); + + String eventName = logParser.getTagMap().get(event.mTag); + String pidName = Integer.toString(event.pid); + + // get the value description + EventValueDescription[] valueDescription = logParser.getEventInfoMap().get(event.mTag); + if (valueDescription != null) { + for (int i = 0; i < valueDescription.length; i++) { + EventValueDescription description = valueDescription[i]; + try { + String value = event.getValueAsString(i); + + logValue(date, pidName, eventName, description.getName(), value, + description.getEventValueType(), description.getValueType()); + } catch (InvalidTypeException e) { + logValue(date, pidName, eventName, description.getName(), e.getMessage(), + description.getEventValueType(), description.getValueType()); + } + } + + // scroll if needed, by showing the last item + if (scroll) { + int itemCount = mLogTable.getItemCount(); + if (itemCount > 0) { + mLogTable.showItem(mLogTable.getItem(itemCount - 1)); + } + } + } + } + + /** + * Adds an {@link EventContainer} to the log. Only add the values/occurrences defined by + * the list of descriptors. If an event is configured to be displayed by value and occurrence, + * only the values are displayed (as they mark an event occurrence anyway). + * <p/>This method is only called when at least one of the descriptor list is non empty. + * + * @param event + * @param logParser + * @param valueDescriptors + * @param occurrenceDescriptors + */ + protected void addToLog(EventContainer event, EventLogParser logParser, + ArrayList<ValueDisplayDescriptor> valueDescriptors, + ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) { + ScrollBar bar = mLogTable.getVerticalBar(); + boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb(); + + // get the date. + Calendar c = Calendar.getInstance(); + long msec = (long) event.sec * 1000L; + c.setTimeInMillis(msec); + + // convert the time into a string + String date = String.format("%1$tF %1$tT", c); + + String eventName = logParser.getTagMap().get(event.mTag); + String pidName = Integer.toString(event.pid); + + if (valueDescriptors.size() > 0) { + for (ValueDisplayDescriptor descriptor : valueDescriptors) { + logDescriptor(event, descriptor, date, pidName, eventName, logParser); + } + } else { + // we display the event. Since the StringBuilder contains the header (date, event name, + // pid) at this point, there isn't anything else to display. + } + + // scroll if needed, by showing the last item + if (scroll) { + int itemCount = mLogTable.getItemCount(); + if (itemCount > 0) { + mLogTable.showItem(mLogTable.getItem(itemCount - 1)); + } + } + } + + + /** + * Logs a value in the ui. + * + * @param date + * @param pid + * @param event + * @param valueName + * @param value + * @param eventValueType + * @param valueType + */ + private void logValue(String date, String pid, String event, String valueName, + String value, EventContainer.EventValueType eventValueType, EventValueDescription.ValueType valueType) { + + TableItem item = new TableItem(mLogTable, SWT.NONE); + item.setText(0, date); + item.setText(1, pid); + item.setText(2, event); + item.setText(3, valueName); + item.setText(4, value); + + String type; + if (valueType != EventValueDescription.ValueType.NOT_APPLICABLE) { + type = String.format("%1$s, %2$s", eventValueType.toString(), valueType.toString()); + } else { + type = eventValueType.toString(); + } + + item.setText(5, type); + } + + /** + * Logs a value from an {@link EventContainer} as defined by the {@link ValueDisplayDescriptor}. + * + * @param event the EventContainer + * @param descriptor the ValueDisplayDescriptor defining which value to display. + * @param date the date of the event in a string. + * @param pidName + * @param eventName + * @param logParser + */ + private void logDescriptor(EventContainer event, ValueDisplayDescriptor descriptor, + String date, String pidName, String eventName, EventLogParser logParser) { + + String value; + try { + value = event.getValueAsString(descriptor.valueIndex); + } catch (InvalidTypeException e) { + value = e.getMessage(); + } + + EventValueDescription[] values = logParser.getEventInfoMap().get(event.mTag); + + EventValueDescription valueDescription = values[descriptor.valueIndex]; + + logValue(date, pidName, eventName, descriptor.valueName, value, + valueDescription.getEventValueType(), valueDescription.getValueType()); + } + + /** + * Creates the UI for a log display. + * + * @param parent the parent {@link Composite} + * @param listener the {@link ILogColumnListener} to notify on column resize events. + * @return the top Composite of the UI. + */ + private Control createLogUI(Composite parent, final ILogColumnListener listener) { + Composite mainComp = new Composite(parent, SWT.NONE); + GridLayout gl; + mainComp.setLayout(gl = new GridLayout(1, false)); + gl.marginHeight = gl.marginWidth = 0; + mainComp.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + mLogTable = null; + } + }); + + Label l = new Label(mainComp, SWT.CENTER); + l.setText(mName); + l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + mLogTable = new Table(mainComp, SWT.MULTI | SWT.FULL_SELECTION | SWT.V_SCROLL | + SWT.BORDER); + mLogTable.setLayoutData(new GridData(GridData.FILL_BOTH)); + + IPreferenceStore store = DdmUiPreferences.getStore(); + + TableColumn col = TableHelper.createTableColumn( + mLogTable, "Time", + SWT.LEFT, "0000-00-00 00:00:00", PREFS_COL_DATE, store); //$NON-NLS-1$ + col.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Object source = e.getSource(); + if (source instanceof TableColumn) { + listener.columnResized(0, (TableColumn) source); + } + } + }); + + col = TableHelper.createTableColumn( + mLogTable, "pid", + SWT.LEFT, "0000", PREFS_COL_PID, store); //$NON-NLS-1$ + col.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Object source = e.getSource(); + if (source instanceof TableColumn) { + listener.columnResized(1, (TableColumn) source); + } + } + }); + + col = TableHelper.createTableColumn( + mLogTable, "Event", + SWT.LEFT, "abcdejghijklmno", PREFS_COL_EVENTTAG, store); //$NON-NLS-1$ + col.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Object source = e.getSource(); + if (source instanceof TableColumn) { + listener.columnResized(2, (TableColumn) source); + } + } + }); + + col = TableHelper.createTableColumn( + mLogTable, "Name", + SWT.LEFT, "Process Name", PREFS_COL_VALUENAME, store); //$NON-NLS-1$ + col.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Object source = e.getSource(); + if (source instanceof TableColumn) { + listener.columnResized(3, (TableColumn) source); + } + } + }); + + col = TableHelper.createTableColumn( + mLogTable, "Value", + SWT.LEFT, "0000000", PREFS_COL_VALUE, store); //$NON-NLS-1$ + col.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Object source = e.getSource(); + if (source instanceof TableColumn) { + listener.columnResized(4, (TableColumn) source); + } + } + }); + + col = TableHelper.createTableColumn( + mLogTable, "Type", + SWT.LEFT, "long, seconds", PREFS_COL_TYPE, store); //$NON-NLS-1$ + col.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Object source = e.getSource(); + if (source instanceof TableColumn) { + listener.columnResized(5, (TableColumn) source); + } + } + }); + + mLogTable.setHeaderVisible(true); + mLogTable.setLinesVisible(true); + + return mainComp; + } + + /** + * Resizes the <code>index</code>-th column of the log {@link Table} (if applicable). + * <p/> + * This does nothing if the <code>Table</code> object is <code>null</code> (because the display + * type does not use a column) or if the <code>index</code>-th column is in fact the originating + * column passed as argument. + * + * @param index the index of the column to resize + * @param sourceColumn the original column that was resize, and on which we need to sync the + * index-th column width. + */ + @Override + void resizeColumn(int index, TableColumn sourceColumn) { + if (mLogTable != null) { + TableColumn col = mLogTable.getColumn(index); + if (col != sourceColumn) { + col.setWidth(sourceColumn.getWidth()); + } + } + } + + /** + * Gets display type + * + * @return display type as an integer + */ + @Override + int getDisplayType() { + return DISPLAY_TYPE_LOG_ALL; + } +} diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySync.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySync.java new file mode 100644 index 0000000..0057c86 --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySync.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2008 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.ddmuilib.log.event; + +import com.android.ddmlib.log.EventContainer; +import com.android.ddmlib.log.EventLogParser; +import com.android.ddmlib.log.InvalidTypeException; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.jfree.chart.labels.CustomXYToolTipGenerator; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.XYBarRenderer; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.data.time.FixedMillisecond; +import org.jfree.data.time.SimpleTimePeriod; +import org.jfree.data.time.TimePeriodValues; +import org.jfree.data.time.TimePeriodValuesCollection; +import org.jfree.data.time.TimeSeries; +import org.jfree.data.time.TimeSeriesCollection; +import org.jfree.util.ShapeUtilities; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.regex.Pattern; + +public class DisplaySync extends EventDisplay { + + // Information to graph for each authority + private TimePeriodValues mDatasetsSync[]; + private List<String> mTooltipsSync[]; + private CustomXYToolTipGenerator mTooltipGenerators[]; + private TimeSeries mDatasetsSyncTickle[]; + + // Dataset of error events to graph + private TimeSeries mDatasetError; + + // State information while processing the event stream + private int mLastState; // 0 if event started, 1 if event stopped + private long mLastStartTime; // ms + private long mLastStopTime; //ms + private String mLastDetails; + private int mLastEvent; // server, poll, etc + + public DisplaySync(String name) { + super(name); + } + + /** + * Resets the display. + */ + @Override + void resetUI() { + initSyncDisplay(); + } + + /** + * Creates the UI for the event display. + * @param parent the parent composite. + * @param logParser the current log parser. + * @return the created control (which may have children). + */ + @Override + public Control createComposite(final Composite parent, EventLogParser logParser, + final ILogColumnListener listener) { + Control composite = createCompositeChart(parent, logParser, "Sync Status"); + initSyncDisplay(); + return composite; + } + + /** + * Initialize the Plot and series data for the sync display. + */ + void initSyncDisplay() { + XYPlot xyPlot = mChart.getXYPlot(); + + XYBarRenderer br = new XYBarRenderer(); + mDatasetsSync = new TimePeriodValues[NUM_AUTHS]; + mTooltipsSync = new List[NUM_AUTHS]; + mTooltipGenerators = new CustomXYToolTipGenerator[NUM_AUTHS]; + mLastDetails = ""; + + TimePeriodValuesCollection tpvc = new TimePeriodValuesCollection(); + xyPlot.setDataset(tpvc); + xyPlot.setRenderer(0, br); + + XYLineAndShapeRenderer ls = new XYLineAndShapeRenderer(); + ls.setBaseLinesVisible(false); + mDatasetsSyncTickle = new TimeSeries[NUM_AUTHS]; + TimeSeriesCollection tsc = new TimeSeriesCollection(); + xyPlot.setDataset(1, tsc); + xyPlot.setRenderer(1, ls); + + mDatasetError = new TimeSeries("Errors", FixedMillisecond.class); + xyPlot.setDataset(2, new TimeSeriesCollection(mDatasetError)); + XYLineAndShapeRenderer errls = new XYLineAndShapeRenderer(); + errls.setBaseLinesVisible(false); + errls.setSeriesPaint(0, Color.RED); + xyPlot.setRenderer(2, errls); + + for (int i = 0; i < NUM_AUTHS; i++) { + br.setSeriesPaint(i, AUTH_COLORS[i]); + ls.setSeriesPaint(i, AUTH_COLORS[i]); + mDatasetsSync[i] = new TimePeriodValues(AUTH_NAMES[i]); + tpvc.addSeries(mDatasetsSync[i]); + mTooltipsSync[i] = new ArrayList<String>(); + mTooltipGenerators[i] = new CustomXYToolTipGenerator(); + br.setSeriesToolTipGenerator(i, mTooltipGenerators[i]); + mTooltipGenerators[i].addToolTipSeries(mTooltipsSync[i]); + + mDatasetsSyncTickle[i] = new TimeSeries(AUTH_NAMES[i] + " tickle", FixedMillisecond.class); + tsc.addSeries(mDatasetsSyncTickle[i]); + ls.setSeriesShape(i, ShapeUtilities.createUpTriangle(2.5f)); + } + } + + /** + * Updates the display with a new event. This is the main entry point for + * each event. This method has the logic to tie together the start event, + * stop event, and details event into one graph item. Note that the details + * can happen before or after the stop event. + * @param event The event + * @param logParser the log parser (unused) + */ + @Override + void newEvent(EventContainer event, EventLogParser logParser) { + try { + if (event.mTag == EVENT_SYNC) { + int state = Integer.parseInt(event.getValueAsString(1)); + if (state == 0) { // start + mLastStartTime = (long)event.sec * 1000L + (event.nsec / 1000000L); + mLastState = 0; + mLastEvent = Integer.parseInt(event.getValueAsString(2)); + mLastDetails = ""; + } else if (state == 1) { // stop + if (mLastState == 0) { + mLastStopTime = (long)event.sec * 1000L + (event.nsec / 1000000L); + if (mLastStartTime == 0) { + // Log starts with a stop event + mLastStartTime = mLastStopTime; + } + addEvent(event); + mLastState = 1; + } + } + } else if (event.mTag == EVENT_TICKLE) { + int auth = getAuth(event.getValueAsString(0)); + if (auth >= 0) { + long msec = (long)event.sec * 1000L + (event.nsec / 1000000L); + mDatasetsSyncTickle[auth].addOrUpdate(new FixedMillisecond(msec), -1); + } + } else if (event.mTag == EVENT_SYNC_DETAILS) { + int auth = getAuth(event.getValueAsString(0)); + mLastDetails = event.getValueAsString(3); + if (mLastState != 0) { // Not inside event + long updateTime = (long)event.sec * 1000L + (event.nsec / 1000000L); + if (updateTime - mLastStopTime <= 250) { + // Got details within 250ms after event, so delete and re-insert + // Details later than 250ms (arbitrary) are discarded as probably + // unrelated. + int lastItem = mDatasetsSync[auth].getItemCount(); + mDatasetsSync[auth].delete(lastItem-1, lastItem-1); + mTooltipsSync[auth].remove(lastItem-1); + addEvent(event); + } + } + } + } catch (InvalidTypeException e) { + } + } + + /** + * Generate the height for an event. + * Height is somewhat arbitrarily the count of "things" that happened + * during the sync. + * When network traffic measurements are available, code should be modified + * to use that instead. + * @param details The details string associated with the event + * @return The height in arbirary units (0-100) + */ + private int getHeightFromDetails(String details) { + if (details == null) { + return 1; // Arbitrary + } + int total = 0; + String parts[] = details.split("[a-zA-Z]"); + for (String part : parts) { + if ("".equals(part)) continue; + total += Integer.parseInt(part); + } + if (total == 0) { + total = 1; + } + return total; + } + + /** + * Generates the tooltips text for an event. + * This method decodes the cryptic details string. + * @param auth The authority associated with the event + * @param details The details string + * @param eventSource server, poll, etc. + * @return The text to display in the tooltips + */ + private String getTextFromDetails(int auth, String details, int eventSource) { + + StringBuffer sb = new StringBuffer(); + sb.append(AUTH_NAMES[auth]).append(": \n"); + + Scanner scanner = new Scanner(details); + Pattern charPat = Pattern.compile("[a-zA-Z]"); + Pattern numPat = Pattern.compile("[0-9]+"); + while (scanner.hasNext()) { + String key = scanner.findInLine(charPat); + int val = Integer.parseInt(scanner.findInLine(numPat)); + if (auth == GMAIL && "M".equals(key)) { + sb.append("messages from server: ").append(val).append("\n"); + } else if (auth == GMAIL && "L".equals(key)) { + sb.append("labels from server: ").append(val).append("\n"); + } else if (auth == GMAIL && "C".equals(key)) { + sb.append("check conversation requests from server: ").append(val).append("\n"); + } else if (auth == GMAIL && "A".equals(key)) { + sb.append("attachments from server: ").append(val).append("\n"); + } else if (auth == GMAIL && "U".equals(key)) { + sb.append("op updates from server: ").append(val).append("\n"); + } else if (auth == GMAIL && "u".equals(key)) { + sb.append("op updates to server: ").append(val).append("\n"); + } else if (auth == GMAIL && "S".equals(key)) { + sb.append("send/receive cycles: ").append(val).append("\n"); + } else if ("Q".equals(key)) { + sb.append("queries to server: ").append(val).append("\n"); + } else if ("E".equals(key)) { + sb.append("entries from server: ").append(val).append("\n"); + } else if ("u".equals(key)) { + sb.append("updates from client: ").append(val).append("\n"); + } else if ("i".equals(key)) { + sb.append("inserts from client: ").append(val).append("\n"); + } else if ("d".equals(key)) { + sb.append("deletes from client: ").append(val).append("\n"); + } else if ("f".equals(key)) { + sb.append("full sync requested\n"); + } else if ("r".equals(key)) { + sb.append("partial sync unavailable\n"); + } else if ("X".equals(key)) { + sb.append("hard error\n"); + } else if ("e".equals(key)) { + sb.append("number of parse exceptions: ").append(val).append("\n"); + } else if ("c".equals(key)) { + sb.append("number of conflicts: ").append(val).append("\n"); + } else if ("a".equals(key)) { + sb.append("number of auth exceptions: ").append(val).append("\n"); + } else if ("D".equals(key)) { + sb.append("too many deletions\n"); + } else if ("R".equals(key)) { + sb.append("too many retries: ").append(val).append("\n"); + } else if ("b".equals(key)) { + sb.append("database error\n"); + } else if ("x".equals(key)) { + sb.append("soft error\n"); + } else if ("l".equals(key)) { + sb.append("sync already in progress\n"); + } else if ("I".equals(key)) { + sb.append("io exception\n"); + } else if (auth == CONTACTS && "p".equals(key)) { + sb.append("photos uploaded from client: ").append(val).append("\n"); + } else if (auth == CONTACTS && "P".equals(key)) { + sb.append("photos downloaded from server: ").append(val).append("\n"); + } else if (auth == CALENDAR && "F".equals(key)) { + sb.append("server refresh\n"); + } else if (auth == CALENDAR && "s".equals(key)) { + sb.append("server diffs fetched\n"); + } else { + sb.append(key).append("=").append(val); + } + } + if (eventSource == 0) { + sb.append("(server)"); + } else if (eventSource == 1) { + sb.append("(local)"); + } else if (eventSource == 2) { + sb.append("(poll)"); + } else if (eventSource == 3) { + sb.append("(user)"); + } + return sb.toString(); + } + + /** + * Helper to add an event to the data series. + * Also updates error series if appropriate (x or X in details). + * @param event The event + */ + private void addEvent(EventContainer event) { + try { + int auth = getAuth(event.getValueAsString(0)); + double height = getHeightFromDetails(mLastDetails); + height = height / (mLastStopTime - mLastStartTime + 1) * 10000; + if (height > 30) { + height = 30; + } + mDatasetsSync[auth].add(new SimpleTimePeriod(mLastStartTime, mLastStopTime), height); + mTooltipsSync[auth].add(getTextFromDetails(auth, mLastDetails, + mLastEvent)); + mTooltipGenerators[auth].addToolTipSeries(mTooltipsSync[auth]); + if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) { + long msec = (long)event.sec * 1000L + (event.nsec / 1000000L); + mDatasetError.addOrUpdate(new FixedMillisecond(msec), -1); + } + } catch (InvalidTypeException e) { + e.printStackTrace(); + } + } + + /** + * Gets display type + * + * @return display type as an integer + */ + @Override + int getDisplayType() { + return DISPLAY_TYPE_SYNC; + } +}
\ No newline at end of file diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncHistogram.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncHistogram.java new file mode 100644 index 0000000..3087997 --- /dev/null +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/DisplaySyncHistogram.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2008 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.ddmuilib.log.event; + +import com.android.ddmlib.log.EventContainer; +import com.android.ddmlib.log.EventLogParser; +import com.android.ddmlib.log.InvalidTypeException; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.AbstractXYItemRenderer; +import org.jfree.chart.renderer.xy.XYBarRenderer; +import org.jfree.data.time.RegularTimePeriod; +import org.jfree.data.time.SimpleTimePeriod; +import org.jfree.data.time.TimePeriodValues; +import org.jfree.data.time.TimePeriodValuesCollection; + +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; + +public class DisplaySyncHistogram extends EventDisplay { + + + // State information while processing the event stream + protected int mLastState; // 0 if event started, 1 if event stopped + protected long mLastStartTime; // ms + protected long mLastStopTime; //ms + protected String mLastDetails; + protected int mLastEvent; // server, poll, etc + + public DisplaySyncHistogram(String name) { + super(name); + } + + /** + * Resets the display. + */ + @Override + void resetUI() { + initSyncHistogramDisplay(); + } + + /** + * Creates the UI for the event display. + * @param parent the parent composite. + * @param logParser the current log parser. + * @return the created control (which may have children). + */ + @Override + public Control createComposite(final Composite parent, EventLogParser logParser, + final ILogColumnListener listener) { + Control composite = createCompositeChart(parent, logParser, "Sync Histogram"); + initSyncHistogramDisplay(); + return composite; + } + + // Information to graph for each authority + private TimePeriodValues mDatasetsSyncHist[]; + + /** + * Initializes the display. + */ + private void initSyncHistogramDisplay() { + XYPlot xyPlot = mChart.getXYPlot(); + + AbstractXYItemRenderer br = new XYBarRenderer(); + mDatasetsSyncHist = new TimePeriodValues[NUM_AUTHS+1]; + mLastDetails = ""; + mTimePeriodMap = new HashMap[NUM_AUTHS + 1]; + + TimePeriodValuesCollection tpvc = new TimePeriodValuesCollection(); + xyPlot.setDataset(tpvc); + xyPlot.setRenderer(br); + + for (int i = 0; i < NUM_AUTHS + 1; i++) { + br.setSeriesPaint(i, AUTH_COLORS[i]); + mDatasetsSyncHist[i] = new TimePeriodValues(AUTH_NAMES[i]); + tpvc.addSeries(mDatasetsSyncHist[i]); + mTimePeriodMap[i] = new HashMap<SimpleTimePeriod, Integer>(); + + } + } + + /** + * Updates the display with a new event. This is the main entry point for + * each event. This method has the logic to tie together the start event, + * stop event, and details event into one graph item. Note that the details + * can happen before or after the stop event. + * @param event The event + */ + @Override + void newEvent(EventContainer event, EventLogParser logParser) { + try { + if (event.mTag == EVENT_SYNC) { + int state = Integer.parseInt(event.getValueAsString(1)); + if (state == 0) { // start + mLastStartTime = (long)event.sec * 1000L + (event.nsec / 1000000L); + mLastState = 0; + mLastEvent = Integer.parseInt(event.getValueAsString(2)); + mLastDetails = ""; + } else if (state == 1) { // stop + if (mLastState == 0) { + mLastStopTime = (long)event.sec * 1000L + (event.nsec / 1000000L); + if (mLastStartTime == 0) { + // Log starts with a stop event + mLastStartTime = mLastStopTime; + } + int auth = getAuth(event.getValueAsString(0)); + if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) { + auth = ERRORS; + } + double delta = (mLastStopTime - mLastStartTime) * 100. / 1000 / 3600; // Percent of hour + addHistEvent(event, auth, delta); + mLastState = 1; + } + } + } else if (event.mTag == EVENT_SYNC_DETAILS) { + int auth = getAuth(event.getValueAsString(0)); + mLastDetails = event.getValueAsString(3); + if (mLastState != 0) { // Not inside event + long updateTime = (long)event.sec * 1000L + (event.nsec / 1000000L); + if (updateTime - mLastStopTime <= 250) { + // Got details within 250ms after event, so delete and re-insert + // Details later than 250ms (arbitrary) are discarded as probably + // unrelated. + //int lastItem = mDatasetsSync[auth].getItemCount(); + //addHistEvent(event); + if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) { + // Item turns out to be in error, so transfer time from old auth to error. + + double delta = (mLastStopTime - mLastStartTime) * 100. / 1000 / 3600; // Percent of hour + addHistEvent(event, auth, -delta); + addHistEvent(event, ERRORS, delta); + } + } + } + } + } catch (InvalidTypeException e) { + } + } + + /** + * Helper to add an event to the data series. + * Also updates error series if appropriate (x or X in details). + * @param event The event + * @param auth + * @param value + */ + private void addHistEvent(EventContainer event, int auth, double value) { + SimpleTimePeriod hour = getTimePeriod(mLastStopTime, mHistWidth); + + // Loop over all datasets to do the stacking. + for (int i = auth; i <= ERRORS; i++) { + addToPeriod(mDatasetsSyncHist, i, hour, value); + } + } + + Map<SimpleTimePeriod, Integer> mTimePeriodMap[]; + + private void addToPeriod(TimePeriodValues tpv[], int auth, SimpleTimePeriod period, double value) { + int index; + if (mTimePeriodMap[auth].containsKey(period)) { + index = mTimePeriodMap[auth].get(period); + double oldValue = tpv[auth].getValue(index).doubleValue(); + tpv[auth].update(index, oldValue + value); + } else { + index = tpv[auth].getItemCount(); + mTimePeriodMap[auth].put(period, index); + tpv[auth].add(period, value); + } + } + + /** + * Creates a multiple-hour time period for the histogram. + * @param time Time in milliseconds. + * @param numHoursWide: should divide into a day. + * @return SimpleTimePeriod covering the number of hours and containing time. + */ + private SimpleTimePeriod getTimePeriod(long time, long numHoursWide) { + Date date = new Date(time); + TimeZone zone = RegularTimePeriod.DEFAULT_TIME_ZONE; + Calendar calendar = Calendar.getInstance(zone); + calendar.setTime(date); + long hoursOfYear = calendar.get(Calendar.HOUR_OF_DAY) + calendar.get(Calendar.DAY_OF_YEAR) * 24; + int year = calendar.get(Calendar.YEAR); + hoursOfYear = (hoursOfYear / numHoursWide) * numHoursWide; + calendar.clear(); + calendar.set(year, 0, 1, 0, 0); // Jan 1 + long start = calendar.getTimeInMillis() + hoursOfYear * 3600 * 1000; + return new SimpleTimePeriod(start, start + numHoursWide * 3600 * 1000); + } + + /** + * Gets display type + * + * @return display type as an integer + */ + @Override + int getDisplayType() { + return DISPLAY_TYPE_SYNC_HIST; + } +} diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplay.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplay.java index e36192c..bbd3e1b 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplay.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplay.java @@ -21,86 +21,48 @@ import com.android.ddmlib.log.EventContainer; import com.android.ddmlib.log.EventContainer.CompareMethod; import com.android.ddmlib.log.EventContainer.EventValueType; import com.android.ddmlib.log.EventLogParser; -import com.android.ddmlib.log.EventValueDescription; import com.android.ddmlib.log.EventValueDescription.ValueType; import com.android.ddmlib.log.InvalidTypeException; -import com.android.ddmuilib.DdmUiPreferences; -import com.android.ddmuilib.TableHelper; - -import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ControlAdapter; -import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.ScrollBar; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; -import org.eclipse.swt.widgets.TableItem; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; -import org.jfree.chart.axis.AxisLocation; -import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.event.ChartChangeEvent; import org.jfree.chart.event.ChartChangeEventType; import org.jfree.chart.event.ChartChangeListener; -import org.jfree.chart.labels.CustomXYToolTipGenerator; import org.jfree.chart.plot.XYPlot; -import org.jfree.chart.renderer.xy.AbstractXYItemRenderer; -import org.jfree.chart.renderer.xy.XYAreaRenderer; -import org.jfree.chart.renderer.xy.XYBarRenderer; -import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; import org.jfree.chart.title.TextTitle; -import org.jfree.data.time.FixedMillisecond; import org.jfree.data.time.Millisecond; -import org.jfree.data.time.RegularTimePeriod; -import org.jfree.data.time.SimpleTimePeriod; -import org.jfree.data.time.TimePeriodValues; -import org.jfree.data.time.TimePeriodValuesCollection; import org.jfree.data.time.TimeSeries; import org.jfree.data.time.TimeSeriesCollection; import org.jfree.experimental.chart.swt.ChartComposite; import org.jfree.experimental.swt.SWTUtils; -import org.jfree.util.ShapeUtilities; import java.awt.Color; import java.security.InvalidParameterException; import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Scanner; import java.util.Set; -import java.util.TimeZone; import java.util.regex.Pattern; /** * Represents a custom display of one or more events. */ -final class EventDisplay { - +abstract class EventDisplay { + private final static String DISPLAY_DATA_STORAGE_SEPARATOR = ":"; //$NON-NLS-1$ private final static String PID_STORAGE_SEPARATOR = ","; //$NON-NLS-1$ private final static String DESCRIPTOR_STORAGE_SEPARATOR = "$"; //$NON-NLS-1$ private final static String DESCRIPTOR_DATA_STORAGE_SEPARATOR = "!"; //$NON-NLS-1$ - - private final static String PREFS_COL_DATE = "EventLogPanel.log.Col1"; //$NON-NLS-1$ - private final static String PREFS_COL_PID = "EventLogPanel.log.Col2"; //$NON-NLS-1$ - private final static String PREFS_COL_EVENTTAG = "EventLogPanel.log.Col3"; //$NON-NLS-1$ - private final static String PREFS_COL_VALUENAME = "EventLogPanel.log.Col4"; //$NON-NLS-1$ - private final static String PREFS_COL_VALUE = "EventLogPanel.log.Col5"; //$NON-NLS-1$ - private final static String PREFS_COL_TYPE = "EventLogPanel.log.Col6"; //$NON-NLS-1$ private final static String FILTER_VALUE_NULL = "<null>"; //$NON-NLS-1$ @@ -111,18 +73,87 @@ final class EventDisplay { public final static int DISPLAY_TYPE_SYNC_HIST = 4; private final static int EVENT_CHECK_FAILED = 0; - private final static int EVENT_CHECK_SAME_TAG = 1; - private final static int EVENT_CHECK_SAME_VALUE = 2; - + protected final static int EVENT_CHECK_SAME_TAG = 1; + protected final static int EVENT_CHECK_SAME_VALUE = 2; + + // Some common variables for sync display. These define the sync backends + //and how they should be displayed. + protected static final int CALENDAR = 0; + protected static final int GMAIL = 1; + protected static final int FEEDS = 2; + protected static final int CONTACTS = 3; + protected static final int ERRORS = 4; + protected static final int NUM_AUTHS = (CONTACTS + 1); + protected static final String AUTH_NAMES[] = {"Calendar", "Gmail", "Feeds", "Contacts", "Errors"}; + protected static final Color AUTH_COLORS[] = {Color.MAGENTA, Color.GREEN, Color.BLUE, Color.ORANGE, Color.RED}; + + // Values from data/etc/event-log-tags + final int EVENT_SYNC = 2720; + final int EVENT_TICKLE = 2742; + final int EVENT_SYNC_DETAILS = 2743; + + /** + * Creates the appropriate EventDisplay subclass. + * + * @param type the type of display (DISPLAY_TYPE_LOG_ALL, etc) + * @param name the name of the display + * @return the created object + */ + public static EventDisplay eventDisplayFactory(int type, String name) { + switch (type) { + case DISPLAY_TYPE_LOG_ALL: + return new DisplayLog(name); + case DISPLAY_TYPE_FILTERED_LOG: + return new DisplayFilteredLog(name); + case DISPLAY_TYPE_SYNC: + return new DisplaySync(name); + case DISPLAY_TYPE_SYNC_HIST: + return new DisplaySyncHistogram(name); + case DISPLAY_TYPE_GRAPH: + return new DisplayGraph(name); + default: + throw new InvalidParameterException("Unknown Display Type"); //$NON-NLS-1$ + } + } + + /** + * Adds event to the display. + * @param event The event + * @param logParser The log parser. + */ + abstract void newEvent(EventContainer event, EventLogParser logParser); + + /** + * Resets the display. + */ + abstract void resetUI(); + + /** + * Gets display type + * + * @return display type as an integer + */ + abstract int getDisplayType(); + + /** + * Creates the UI for the event display. + * + * @param parent the parent composite. + * @param logParser the current log parser. + * @return the created control (which may have children). + */ + abstract Control createComposite(final Composite parent, EventLogParser logParser, + final ILogColumnListener listener); + interface ILogColumnListener { void columnResized(int index, TableColumn sourceColumn); } /** - * Describes an event to be displayed. + * Describes an event to be displayed. */ static class OccurrenceDisplayDescriptor { - + int eventTag = -1; int seriesValueIndex = -1; boolean includePid = false; @@ -158,17 +189,19 @@ final class EventDisplay { /** * Loads the descriptor parameter from a storage string. The storage string must have * been generated with {@link #getStorageString()}. + * * @param storageString the storage string */ final void loadFrom(String storageString) { String[] values = storageString.split(Pattern.quote(DESCRIPTOR_DATA_STORAGE_SEPARATOR)); loadFrom(values, 0); } - + /** * Loads the parameters from an array of strings. + * * @param storageStrings the strings representing each parameter. - * @param index the starting index in the array of strings. + * @param index the starting index in the array of strings. * @return the new index in the array. */ protected int loadFrom(String[] storageStrings, int index) { @@ -221,7 +254,7 @@ final class EventDisplay { } /** - * Describes an event value to be displayed. + * Describes an event value to be displayed. */ static final class ValueDisplayDescriptor extends OccurrenceDisplayDescriptor { String valueName; @@ -253,7 +286,7 @@ final class EventDisplay { void replaceWith(OccurrenceDisplayDescriptor descriptor) { super.replaceWith(descriptor); if (descriptor instanceof ValueDisplayDescriptor) { - ValueDisplayDescriptor valueDescriptor = (ValueDisplayDescriptor)descriptor; + ValueDisplayDescriptor valueDescriptor = (ValueDisplayDescriptor) descriptor; valueName = valueDescriptor.valueName; valueIndex = valueDescriptor.valueIndex; } @@ -261,8 +294,9 @@ final class EventDisplay { /** * Loads the parameters from an array of strings. + * * @param storageStrings the strings representing each parameter. - * @param index the starting index in the array of strings. + * @param index the starting index in the array of strings. * @return the new index in the array. */ @Override @@ -294,100 +328,78 @@ final class EventDisplay { /* ================== * Event Display parameters. * ================== */ - private String mName; - - private int mDisplayType = DISPLAY_TYPE_GRAPH; + protected String mName; + private boolean mPidFiltering = false; private ArrayList<Integer> mPidFilterList = null; - - private final ArrayList<ValueDisplayDescriptor> mValueDescriptors = - new ArrayList<ValueDisplayDescriptor>(); + + protected final ArrayList<ValueDisplayDescriptor> mValueDescriptors = + new ArrayList<ValueDisplayDescriptor>(); private final ArrayList<OccurrenceDisplayDescriptor> mOccurrenceDescriptors = - new ArrayList<OccurrenceDisplayDescriptor>(); + new ArrayList<OccurrenceDisplayDescriptor>(); /* ================== * Event Display members for display purpose. * ================== */ // chart objects - /** This is a map of (descriptor, map2) where map2 is a map of (pid, chart-series) */ - private final HashMap<ValueDisplayDescriptor, HashMap<Integer, TimeSeries>> mValueDescriptorSeriesMap = - new HashMap<ValueDisplayDescriptor, HashMap<Integer, TimeSeries>>(); - /** This is a map of (descriptor, map2) where map2 is a map of (pid, chart-series) */ - private final HashMap<OccurrenceDisplayDescriptor, HashMap<Integer, TimeSeries>> mOcurrenceDescriptorSeriesMap = - new HashMap<OccurrenceDisplayDescriptor, HashMap<Integer, TimeSeries>>(); - - /** This is a map of (ValueType, dataset) */ - private final HashMap<ValueType, TimeSeriesCollection> mValueTypeDataSetMap = - new HashMap<ValueType, TimeSeriesCollection>(); - - private JFreeChart mChart; - private TimeSeriesCollection mOccurrenceDataSet; - private int mDataSetCount; + /** + * This is a map of (descriptor, map2) where map2 is a map of (pid, chart-series) + */ + protected final HashMap<ValueDisplayDescriptor, HashMap<Integer, TimeSeries>> mValueDescriptorSeriesMap = + new HashMap<ValueDisplayDescriptor, HashMap<Integer, TimeSeries>>(); + /** + * This is a map of (descriptor, map2) where map2 is a map of (pid, chart-series) + */ + protected final HashMap<OccurrenceDisplayDescriptor, HashMap<Integer, TimeSeries>> mOcurrenceDescriptorSeriesMap = + new HashMap<OccurrenceDisplayDescriptor, HashMap<Integer, TimeSeries>>(); + + /** + * This is a map of (ValueType, dataset) + */ + protected final HashMap<ValueType, TimeSeriesCollection> mValueTypeDataSetMap = + new HashMap<ValueType, TimeSeriesCollection>(); + + protected JFreeChart mChart; + protected TimeSeriesCollection mOccurrenceDataSet; + protected int mDataSetCount; private ChartComposite mChartComposite; - private long mMaximumChartItemAge = -1; - private long mHistWidth = 1; + protected long mMaximumChartItemAge = -1; + protected long mHistWidth = 1; // log objects. - private Table mLogTable; + protected Table mLogTable; /* ================== * Misc data. * ================== */ - private int mValueDescriptorCheck = EVENT_CHECK_FAILED; - - /** - * Loads a new {@link EventDisplay} from a storage string. The string must have been created - * with {@link #getStorageString()}. - * @param storageString the storage string - * @return a new {@link EventDisplay} or null if the load failed. - */ - static EventDisplay load(String storageString) { - EventDisplay ed = new EventDisplay(); - if (ed.loadFrom(storageString)) { - return ed; - } - - return null; - } + protected int mValueDescriptorCheck = EVENT_CHECK_FAILED; EventDisplay(String name) { mName = name; } - /** - * Builds an {@link EventDisplay}. - * @param name the name of the display - * @param displayType the display type: {@link #DISPLAY_TYPE_GRAPH} or - * {@value #DISPLAY_TYPE_FILTERED_LOG}. - * @param filterByPid the flag indicating whether to filter by pid. - */ - EventDisplay(String name, int displayType, boolean filterByPid) { - mName = name; - mDisplayType = displayType; - mPidFiltering = filterByPid; - } - - EventDisplay(EventDisplay from) { - mName = from.mName; - mDisplayType = from.mDisplayType; - mPidFiltering = from.mPidFiltering; - mMaximumChartItemAge = from.mMaximumChartItemAge; - mHistWidth = from.mHistWidth; + static EventDisplay clone(EventDisplay from) { + EventDisplay ed = eventDisplayFactory(from.getDisplayType(), from.getName()); + ed.mName = from.mName; + ed.mPidFiltering = from.mPidFiltering; + ed.mMaximumChartItemAge = from.mMaximumChartItemAge; + ed.mHistWidth = from.mHistWidth; if (from.mPidFilterList != null) { - mPidFilterList = new ArrayList<Integer>(); - mPidFilterList.addAll(from.mPidFilterList); + ed.mPidFilterList = new ArrayList<Integer>(); + ed.mPidFilterList.addAll(from.mPidFilterList); } for (ValueDisplayDescriptor desc : from.mValueDescriptors) { - mValueDescriptors.add(new ValueDisplayDescriptor(desc)); + ed.mValueDescriptors.add(new ValueDisplayDescriptor(desc)); } - mValueDescriptorCheck = from.mValueDescriptorCheck; + ed.mValueDescriptorCheck = from.mValueDescriptorCheck; for (OccurrenceDisplayDescriptor desc : from.mOccurrenceDescriptors) { - mOccurrenceDescriptors.add(new OccurrenceDisplayDescriptor(desc)); + ed.mOccurrenceDescriptors.add(new OccurrenceDisplayDescriptor(desc)); } + return ed; } /** @@ -398,7 +410,7 @@ final class EventDisplay { sb.append(mName); sb.append(DISPLAY_DATA_STORAGE_SEPARATOR); - sb.append(mDisplayType); + sb.append(getDisplayType()); sb.append(DISPLAY_DATA_STORAGE_SEPARATOR); sb.append(Boolean.toString(mPidFiltering)); sb.append(DISPLAY_DATA_STORAGE_SEPARATOR); @@ -419,35 +431,27 @@ final class EventDisplay { void setName(String name) { mName = name; } - + String getName() { return mName; } - - void setDisplayType(int value) { - mDisplayType = value; - } - - int getDisplayType() { - return mDisplayType; - } - + void setPidFiltering(boolean filterByPid) { mPidFiltering = filterByPid; } - + boolean getPidFiltering() { return mPidFiltering; } - + void setPidFilterList(ArrayList<Integer> pids) { if (mPidFiltering == false) { new InvalidParameterException(); } - + mPidFilterList = pids; } - + ArrayList<Integer> getPidFilterList() { return mPidFilterList; } @@ -456,11 +460,11 @@ final class EventDisplay { if (mPidFiltering == false) { new InvalidParameterException(); } - + if (mPidFilterList == null) { mPidFilterList = new ArrayList<Integer>(); } - + mPidFilterList.add(pid); } @@ -485,27 +489,29 @@ final class EventDisplay { Iterator<OccurrenceDisplayDescriptor> getOccurrenceDescriptors() { return mOccurrenceDescriptors.iterator(); } - + /** - * Adds a descriptor. This can be a {@link OccurrenceDisplayDescriptor} or a + * Adds a descriptor. This can be a {@link OccurrenceDisplayDescriptor} or a * {@link ValueDisplayDescriptor}. + * * @param descriptor the descriptor to be added. */ void addDescriptor(OccurrenceDisplayDescriptor descriptor) { if (descriptor instanceof ValueDisplayDescriptor) { - mValueDescriptors.add((ValueDisplayDescriptor)descriptor); + mValueDescriptors.add((ValueDisplayDescriptor) descriptor); mValueDescriptorCheck = checkDescriptors(); } else { mOccurrenceDescriptors.add(descriptor); } } - + /** * Returns a descriptor by index and class (extending {@link OccurrenceDisplayDescriptor}). + * * @param descriptorClass the class of the descriptor to return. - * @param index the index of the descriptor to return. + * @param index the index of the descriptor to return. * @return either a {@link OccurrenceDisplayDescriptor} or a {@link ValueDisplayDescriptor} - * or <code>null</code> if <code>descriptorClass</code> is another class. + * or <code>null</code> if <code>descriptorClass</code> is another class. */ OccurrenceDisplayDescriptor getDescriptor( Class<? extends OccurrenceDisplayDescriptor> descriptorClass, int index) { @@ -515,14 +521,15 @@ final class EventDisplay { } else if (descriptorClass == ValueDisplayDescriptor.class) { return mValueDescriptors.get(index); } - + return null; } - + /** * Removes a descriptor based on its class and index. + * * @param descriptorClass the class of the descriptor. - * @param index the index of the descriptor to be removed. + * @param index the index of the descriptor to be removed. */ void removeDescriptor(Class<? extends OccurrenceDisplayDescriptor> descriptorClass, int index) { if (descriptorClass == OccurrenceDisplayDescriptor.class) { @@ -533,137 +540,107 @@ final class EventDisplay { } } - /** - * Creates the UI for the event display. - * @param parent the parent composite. - * @param logParser the current log parser. - * @return the created control (which may have children). - */ - Control createComposite(final Composite parent, EventLogParser logParser, - final ILogColumnListener listener) { - switch (mDisplayType) { - case DISPLAY_TYPE_LOG_ALL: - // intended fall-through - case DISPLAY_TYPE_FILTERED_LOG: - return createLogUI(parent, listener); - case DISPLAY_TYPE_GRAPH: - // intended fall-through - case DISPLAY_TYPE_SYNC: - // intended fall-through - case DISPLAY_TYPE_SYNC_HIST: - String title = getChartTitle(logParser); - mChart = ChartFactory.createTimeSeriesChart( - null, - null /* timeAxisLabel */, - null /* valueAxisLabel */, - null, /* dataset. set below */ - true /* legend */, - false /* tooltips */, - false /* urls */); - - // get the font to make a proper title. We need to convert the swt font, - // into an awt font. - Font f = parent.getFont(); - FontData[] fData = f.getFontData(); - - // event though on Mac OS there could be more than one fontData, we'll only use - // the first one. - FontData firstFontData = fData[0]; - - java.awt.Font awtFont = SWTUtils.toAwtFont(parent.getDisplay(), - firstFontData, true /* ensureSameSize */); - - if (mDisplayType == DISPLAY_TYPE_SYNC) { - title = "Sync Status"; - } else if (mDisplayType == DISPLAY_TYPE_SYNC_HIST) { - title = "Sync Histogram"; + Control createCompositeChart(final Composite parent, EventLogParser logParser, + String title) { + mChart = ChartFactory.createTimeSeriesChart( + null, + null /* timeAxisLabel */, + null /* valueAxisLabel */, + null, /* dataset. set below */ + true /* legend */, + false /* tooltips */, + false /* urls */); + + // get the font to make a proper title. We need to convert the swt font, + // into an awt font. + Font f = parent.getFont(); + FontData[] fData = f.getFontData(); + + // event though on Mac OS there could be more than one fontData, we'll only use + // the first one. + FontData firstFontData = fData[0]; + + java.awt.Font awtFont = SWTUtils.toAwtFont(parent.getDisplay(), + firstFontData, true /* ensureSameSize */); + + + mChart.setTitle(new TextTitle(title, awtFont)); + + final XYPlot xyPlot = mChart.getXYPlot(); + xyPlot.setRangeCrosshairVisible(true); + xyPlot.setRangeCrosshairLockedOnData(true); + xyPlot.setDomainCrosshairVisible(true); + xyPlot.setDomainCrosshairLockedOnData(true); + + mChart.addChangeListener(new ChartChangeListener() { + public void chartChanged(ChartChangeEvent event) { + ChartChangeEventType type = event.getType(); + if (type == ChartChangeEventType.GENERAL) { + // because the value we need (rangeCrosshair and domainCrosshair) are + // updated on the draw, but the notification happens before the draw, + // we process the click in a future runnable! + parent.getDisplay().asyncExec(new Runnable() { + public void run() { + processClick(xyPlot); + } + }); } + } + }); - mChart.setTitle(new TextTitle(title, awtFont)); - - final XYPlot xyPlot = mChart.getXYPlot(); - xyPlot.setRangeCrosshairVisible(true); - xyPlot.setRangeCrosshairLockedOnData(true); - xyPlot.setDomainCrosshairVisible(true); - xyPlot.setDomainCrosshairLockedOnData(true); - - mChart.addChangeListener(new ChartChangeListener() { - public void chartChanged(ChartChangeEvent event) { - ChartChangeEventType type = event.getType(); - if (type == ChartChangeEventType.GENERAL) { - // because the value we need (rangeCrosshair and domainCrosshair) are - // updated on the draw, but the notification happens before the draw, - // we process the click in a future runnable! - parent.getDisplay().asyncExec(new Runnable() { - public void run() { - processClick(xyPlot); - } - }); - } - } - }); - - mChartComposite = new ChartComposite(parent, SWT.BORDER, mChart, - ChartComposite.DEFAULT_WIDTH, - ChartComposite.DEFAULT_HEIGHT, - ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH, - ChartComposite.DEFAULT_MINIMUM_DRAW_HEIGHT, - 3000, // max draw width. We don't want it to zoom, so we put a big number - 3000, // max draw height. We don't want it to zoom, so we put a big number - true, // off-screen buffer - true, // properties - true, // save - true, // print - true, // zoom - true); // tooltips - - mChartComposite.addDisposeListener(new DisposeListener() { - public void widgetDisposed(DisposeEvent e) { - mValueTypeDataSetMap.clear(); - mDataSetCount = 0; - mOccurrenceDataSet = null; - mChart = null; - mChartComposite = null; - mValueDescriptorSeriesMap.clear(); - mOcurrenceDescriptorSeriesMap.clear(); - } - }); + mChartComposite = new ChartComposite(parent, SWT.BORDER, mChart, + ChartComposite.DEFAULT_WIDTH, + ChartComposite.DEFAULT_HEIGHT, + ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH, + ChartComposite.DEFAULT_MINIMUM_DRAW_HEIGHT, + 3000, // max draw width. We don't want it to zoom, so we put a big number + 3000, // max draw height. We don't want it to zoom, so we put a big number + true, // off-screen buffer + true, // properties + true, // save + true, // print + true, // zoom + true); // tooltips + + mChartComposite.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + mValueTypeDataSetMap.clear(); + mDataSetCount = 0; + mOccurrenceDataSet = null; + mChart = null; + mChartComposite = null; + mValueDescriptorSeriesMap.clear(); + mOcurrenceDescriptorSeriesMap.clear(); + } + }); - if (mDisplayType == DISPLAY_TYPE_SYNC) { - initSyncDisplay(); - } else if (mDisplayType == DISPLAY_TYPE_SYNC_HIST) { - initSyncHistDisplay(); - } + return mChartComposite; - return mChartComposite; - default: - throw new InvalidParameterException("Unknown Display Type"); //$NON-NLS-1$ - } } private void processClick(XYPlot xyPlot) { double rangeValue = xyPlot.getRangeCrosshairValue(); - if (rangeValue != 0) { + if (rangeValue != 0) { double domainValue = xyPlot.getDomainCrosshairValue(); - - Millisecond msec = new Millisecond(new Date((long)domainValue)); - + + Millisecond msec = new Millisecond(new Date((long) domainValue)); + // look for values in the dataset that contains data at this TimePeriod Set<ValueDisplayDescriptor> descKeys = mValueDescriptorSeriesMap.keySet(); - + for (ValueDisplayDescriptor descKey : descKeys) { HashMap<Integer, TimeSeries> map = mValueDescriptorSeriesMap.get(descKey); - + Set<Integer> pidKeys = map.keySet(); - + for (Integer pidKey : pidKeys) { TimeSeries series = map.get(pidKey); - + Number value = series.getValue(msec); if (value != null) { // found a match. lets check against the actual value. if (value.doubleValue() == rangeValue) { - + return; } } @@ -672,176 +649,29 @@ final class EventDisplay { } } - /** - * Creates the UI for a log display. - * @param parent the parent {@link Composite} - * @param listener the {@link ILogColumnListener} to notify on column resize events. - * @return the top Composite of the UI. - */ - private Control createLogUI(Composite parent, final ILogColumnListener listener) { - Composite mainComp = new Composite(parent, SWT.NONE); - GridLayout gl; - mainComp.setLayout(gl = new GridLayout(1, false)); - gl.marginHeight = gl.marginWidth = 0; - mainComp.addDisposeListener(new DisposeListener() { - public void widgetDisposed(DisposeEvent e) { - mLogTable = null; - } - }); - - Label l = new Label(mainComp, SWT.CENTER); - l.setText(mName); - l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - - mLogTable = new Table(mainComp, SWT.MULTI | SWT.FULL_SELECTION | SWT.V_SCROLL | - SWT.BORDER); - mLogTable.setLayoutData(new GridData(GridData.FILL_BOTH)); - - IPreferenceStore store = DdmUiPreferences.getStore(); - - TableColumn col = TableHelper.createTableColumn( - mLogTable, "Time", - SWT.LEFT, "0000-00-00 00:00:00", PREFS_COL_DATE, store); //$NON-NLS-1$ - col.addControlListener(new ControlAdapter() { - @Override - public void controlResized(ControlEvent e) { - Object source = e.getSource(); - if (source instanceof TableColumn) { - listener.columnResized(0, (TableColumn)source); - } - } - }); - - col = TableHelper.createTableColumn( - mLogTable, "pid", - SWT.LEFT, "0000", PREFS_COL_PID, store); //$NON-NLS-1$ - col.addControlListener(new ControlAdapter() { - @Override - public void controlResized(ControlEvent e) { - Object source = e.getSource(); - if (source instanceof TableColumn) { - listener.columnResized(1, (TableColumn)source); - } - } - }); - - col = TableHelper.createTableColumn( - mLogTable, "Event", - SWT.LEFT, "abcdejghijklmno", PREFS_COL_EVENTTAG, store); //$NON-NLS-1$ - col.addControlListener(new ControlAdapter() { - @Override - public void controlResized(ControlEvent e) { - Object source = e.getSource(); - if (source instanceof TableColumn) { - listener.columnResized(2, (TableColumn)source); - } - } - }); - - col = TableHelper.createTableColumn( - mLogTable, "Name", - SWT.LEFT, "Process Name", PREFS_COL_VALUENAME, store); //$NON-NLS-1$ - col.addControlListener(new ControlAdapter() { - @Override - public void controlResized(ControlEvent e) { - Object source = e.getSource(); - if (source instanceof TableColumn) { - listener.columnResized(3, (TableColumn)source); - } - } - }); - - col = TableHelper.createTableColumn( - mLogTable, "Value", - SWT.LEFT, "0000000", PREFS_COL_VALUE, store); //$NON-NLS-1$ - col.addControlListener(new ControlAdapter() { - @Override - public void controlResized(ControlEvent e) { - Object source = e.getSource(); - if (source instanceof TableColumn) { - listener.columnResized(4, (TableColumn)source); - } - } - }); - - col = TableHelper.createTableColumn( - mLogTable, "Type", - SWT.LEFT, "long, seconds", PREFS_COL_TYPE, store); //$NON-NLS-1$ - col.addControlListener(new ControlAdapter() { - @Override - public void controlResized(ControlEvent e) { - Object source = e.getSource(); - if (source instanceof TableColumn) { - listener.columnResized(5, (TableColumn)source); - } - } - }); - - mLogTable.setHeaderVisible(true); - mLogTable.setLinesVisible(true); - return mainComp; - } - /** * Resizes the <code>index</code>-th column of the log {@link Table} (if applicable). + * Subclasses can override if necessary. * <p/> * This does nothing if the <code>Table</code> object is <code>null</code> (because the display * type does not use a column) or if the <code>index</code>-th column is in fact the originating * column passed as argument. - * @param index the index of the column to resize + * + * @param index the index of the column to resize * @param sourceColumn the original column that was resize, and on which we need to sync the - * index-th column width. + * index-th column width. */ void resizeColumn(int index, TableColumn sourceColumn) { - if (mLogTable != null) { - TableColumn col = mLogTable.getColumn(index); - if (col != sourceColumn) { - col.setWidth(sourceColumn.getWidth()); - } - } } - + /** * Sets the current {@link EventLogParser} object. + * Subclasses can override if necessary. */ - void setNewLogParser(EventLogParser logParser) { - if (mDisplayType == DISPLAY_TYPE_GRAPH) { - if (mChart != null) { - mChart.setTitle(getChartTitle(logParser)); - } - } - } - - void resetUI() { - switch (mDisplayType) { - case DISPLAY_TYPE_LOG_ALL: - // intended fall-through - case DISPLAY_TYPE_FILTERED_LOG: - mLogTable.removeAll(); - break; - case DISPLAY_TYPE_SYNC: - initSyncDisplay(); - break; - case DISPLAY_TYPE_SYNC_HIST: - initSyncHistDisplay(); - break; - case DISPLAY_TYPE_GRAPH: - Collection<TimeSeriesCollection> datasets = mValueTypeDataSetMap.values(); - for (TimeSeriesCollection dataset : datasets) { - dataset.removeAllSeries(); - } - if (mOccurrenceDataSet != null) { - mOccurrenceDataSet.removeAllSeries(); - } - mValueDescriptorSeriesMap.clear(); - mOcurrenceDescriptorSeriesMap.clear(); - break; - default: - throw new InvalidParameterException("Unknown Display Type"); //$NON-NLS-1$ - } + protected void setNewLogParser(EventLogParser logParser) { } - + /** * Prepares the {@link EventDisplay} for a multi event display. */ @@ -861,102 +691,70 @@ final class EventDisplay { } /** - * Processes a new event. This must be called from the ui thread. - * @param event the event to process. - */ - void newEvent(EventContainer event, EventLogParser logParser) { - ArrayList<ValueDisplayDescriptor> valueDescriptors = - new ArrayList<ValueDisplayDescriptor>(); - - ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors = - new ArrayList<OccurrenceDisplayDescriptor>(); - - if (filterEvent(event, valueDescriptors, occurrenceDescriptors)) { - switch (mDisplayType) { - case DISPLAY_TYPE_LOG_ALL: - addToLog(event, logParser); - break; - case DISPLAY_TYPE_FILTERED_LOG: - addToLog(event, logParser, valueDescriptors, occurrenceDescriptors); - break; - case DISPLAY_TYPE_SYNC: - updateSyncDisplay(event); - break; - case DISPLAY_TYPE_SYNC_HIST: - updateSyncHistDisplay(event); - break; - case DISPLAY_TYPE_GRAPH: - updateChart(event, logParser, valueDescriptors, occurrenceDescriptors); - break; - default: - throw new InvalidParameterException("Unknown Display Type"); //$NON-NLS-1$ - } - } - } - - /** * Returns the {@link Table} object used to display events, if any. + * * @return a Table object or <code>null</code>. */ Table getTable() { return mLogTable; } - /** Private constructor used for loading from storage */ - private EventDisplay() { - // nothing to be done here. - } - /** - * Loads the {@link EventDisplay} parameters from the storage string. - * @param storageString the string containing the parameters. + * Loads a new {@link EventDisplay} from a storage string. The string must have been created + * with {@link #getStorageString()}. + * + * @param storageString the storage string + * @return a new {@link EventDisplay} or null if the load failed. */ - private boolean loadFrom(String storageString) { + static EventDisplay load(String storageString) { if (storageString.length() > 0) { // the storage string is separated by ':' String[] values = storageString.split(Pattern.quote(DISPLAY_DATA_STORAGE_SEPARATOR)); - + try { int index = 0; - - mName = values[index++]; - mDisplayType = Integer.parseInt(values[index++]); - mPidFiltering = Boolean.parseBoolean(values[index++]); + + String name = values[index++]; + int displayType = Integer.parseInt(values[index++]); + boolean pidFiltering = Boolean.parseBoolean(values[index++]); + + EventDisplay ed = eventDisplayFactory(displayType, name); + ed.setPidFiltering(pidFiltering); // because empty sections are removed by String.split(), we have to check // the index for those. if (index < values.length) { - loadPidFilters(values[index++]); + ed.loadPidFilters(values[index++]); } - + if (index < values.length) { - loadValueDescriptors(values[index++]); + ed.loadValueDescriptors(values[index++]); } - + if (index < values.length) { - loadOccurrenceDescriptors(values[index++]); + ed.loadOccurrenceDescriptors(values[index++]); } - - updateValueDescriptorCheck(); - + + ed.updateValueDescriptorCheck(); + if (index < values.length) { - mMaximumChartItemAge = Long.parseLong(values[index++]); + ed.mMaximumChartItemAge = Long.parseLong(values[index++]); } if (index < values.length) { - mHistWidth = Long.parseLong(values[index++]); + ed.mHistWidth = Long.parseLong(values[index++]); } - - return true; + + return ed; } catch (RuntimeException re) { - // we'll return false below. + // we'll return null below. Log.e("ddms", re); } } - - return false; + + return null; } - + private String getPidStorageString() { if (mPidFilterList != null) { StringBuilder sb = new StringBuilder(); @@ -969,17 +767,17 @@ final class EventDisplay { } sb.append(i); } - + return sb.toString(); } return ""; //$NON-NLS-1$ } - + private void loadPidFilters(String storageString) { if (storageString.length() > 0) { String[] values = storageString.split(Pattern.quote(PID_STORAGE_SEPARATOR)); - + for (String value : values) { if (mPidFilterList == null) { mPidFilterList = new ArrayList<Integer>(); @@ -988,12 +786,12 @@ final class EventDisplay { } } } - + private String getDescriptorStorageString( ArrayList<? extends OccurrenceDisplayDescriptor> descriptorList) { StringBuilder sb = new StringBuilder(); boolean first = true; - + for (OccurrenceDisplayDescriptor descriptor : descriptorList) { if (first == false) { sb.append(DESCRIPTOR_STORAGE_SEPARATOR); @@ -1002,7 +800,7 @@ final class EventDisplay { } sb.append(descriptor.getStorageString()); } - + return sb.toString(); } @@ -1012,21 +810,21 @@ final class EventDisplay { } String[] values = storageString.split(Pattern.quote(DESCRIPTOR_STORAGE_SEPARATOR)); - + for (String value : values) { OccurrenceDisplayDescriptor desc = new OccurrenceDisplayDescriptor(); desc.loadFrom(value); mOccurrenceDescriptors.add(desc); } } - + private void loadValueDescriptors(String storageString) { if (storageString.length() == 0) { return; } String[] values = storageString.split(Pattern.quote(DESCRIPTOR_STORAGE_SEPARATOR)); - + for (String value : values) { ValueDisplayDescriptor desc = new ValueDisplayDescriptor(); desc.loadFrom(value); @@ -1035,292 +833,12 @@ final class EventDisplay { } /** - * Returns the {@link TimeSeriesCollection} for the occurrence display. If the data set is not - * yet created, it is first allocated and set up into the {@link JFreeChart} object. - */ - private TimeSeriesCollection getOccurrenceDataSet() { - if (mOccurrenceDataSet == null) { - mOccurrenceDataSet = new TimeSeriesCollection(); - - XYPlot xyPlot = mChart.getXYPlot(); - xyPlot.setDataset(mDataSetCount, mOccurrenceDataSet); - - OccurrenceRenderer renderer = new OccurrenceRenderer(); - renderer.setBaseShapesVisible(false); - xyPlot.setRenderer(mDataSetCount, renderer); - - mDataSetCount++; - } - - return mOccurrenceDataSet; - } - - /** - * Returns a {@link TimeSeriesCollection} for a specific {@link ValueType}. - * If the data set is not yet created, it is first allocated and set up into the - * {@link JFreeChart} object. - * @param type the {@link ValueType} of the data set. - * @param accumulateValues - */ - private TimeSeriesCollection getValueDataset(ValueType type, boolean accumulateValues) { - TimeSeriesCollection dataset = mValueTypeDataSetMap.get(type); - if (dataset == null) { - // create the data set and store it in the map - dataset = new TimeSeriesCollection(); - mValueTypeDataSetMap.put(type, dataset); - - // create the renderer and configure it depending on the ValueType - AbstractXYItemRenderer renderer; - if (type == ValueType.PERCENT && accumulateValues) { - renderer = new XYAreaRenderer(); - } else { - XYLineAndShapeRenderer r = new XYLineAndShapeRenderer(); - r.setBaseShapesVisible(type != ValueType.PERCENT); - - renderer = r; - } - - // set both the dataset and the renderer in the plot object. - XYPlot xyPlot = mChart.getXYPlot(); - xyPlot.setDataset(mDataSetCount, dataset); - xyPlot.setRenderer(mDataSetCount, renderer); - - // put a new axis label, and configure it. - NumberAxis axis = new NumberAxis(type.toString()); - - if (type == ValueType.PERCENT) { - // force percent range to be (0,100) fixed. - axis.setAutoRange(false); - axis.setRange(0., 100.); - } - - // for the index, we ignore the occurrence dataset - int count = mDataSetCount; - if (mOccurrenceDataSet != null) { - count--; - } - - xyPlot.setRangeAxis(count, axis); - if ((count % 2) == 0) { - xyPlot.setRangeAxisLocation(count, AxisLocation.BOTTOM_OR_LEFT); - } else { - xyPlot.setRangeAxisLocation(count, AxisLocation.TOP_OR_RIGHT); - } - - // now we link the dataset and the axis - xyPlot.mapDatasetToRangeAxis(mDataSetCount, count); - - mDataSetCount++; - } - - return dataset; - } - - - /** - * Updates the chart with the {@link EventContainer} by adding the values/occurrences defined - * by the {@link ValueDisplayDescriptor} and {@link OccurrenceDisplayDescriptor} objects from - * the two lists. - * <p/>This method is only called when at least one of the descriptor list is non empty. - * @param event - * @param logParser - * @param valueDescriptors - * @param occurrenceDescriptors - */ - private void updateChart(EventContainer event, EventLogParser logParser, - ArrayList<ValueDisplayDescriptor> valueDescriptors, - ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) { - Map<Integer, String> tagMap = logParser.getTagMap(); - - Millisecond millisecondTime = null; - long msec = -1; - - // If the event container is a cpu container (tag == 2721), and there is no descriptor - // for the total CPU load, then we do accumulate all the values. - boolean accumulateValues = false; - double accumulatedValue = 0; - - if (event.mTag == 2721) { - accumulateValues = true; - for (ValueDisplayDescriptor descriptor : valueDescriptors) { - accumulateValues &= (descriptor.valueIndex != 0); - } - } - - for (ValueDisplayDescriptor descriptor : valueDescriptors) { - try { - // get the hashmap for this descriptor - HashMap<Integer, TimeSeries> map = mValueDescriptorSeriesMap.get(descriptor); - - // if it's not there yet, we create it. - if (map == null) { - map = new HashMap<Integer, TimeSeries>(); - mValueDescriptorSeriesMap.put(descriptor, map); - } - - // get the TimeSeries for this pid - TimeSeries timeSeries = map.get(event.pid); - - // if it doesn't exist yet, we create it - if (timeSeries == null) { - // get the series name - String seriesFullName = null; - String seriesLabel = getSeriesLabel(event, descriptor); - - switch (mValueDescriptorCheck) { - case EVENT_CHECK_SAME_TAG: - seriesFullName = String.format("%1$s / %2$s", seriesLabel, - descriptor.valueName); - break; - case EVENT_CHECK_SAME_VALUE: - seriesFullName = String.format("%1$s", seriesLabel); - break; - default: - seriesFullName = String.format("%1$s / %2$s: %3$s", seriesLabel, - tagMap.get(descriptor.eventTag), - descriptor.valueName); - break; - } - - // get the data set for this ValueType - TimeSeriesCollection dataset = getValueDataset( - logParser.getEventInfoMap().get(event.mTag)[descriptor.valueIndex] - .getValueType(), - accumulateValues); - - // create the series - timeSeries = new TimeSeries(seriesFullName, Millisecond.class); - if (mMaximumChartItemAge != -1) { - timeSeries.setMaximumItemAge(mMaximumChartItemAge * 1000); - } - - dataset.addSeries(timeSeries); - - // add it to the map. - map.put(event.pid, timeSeries); - } - - // update the timeSeries. - - // get the value from the event - double value = event.getValueAsDouble(descriptor.valueIndex); - - // accumulate the values if needed. - if (accumulateValues) { - accumulatedValue += value; - value = accumulatedValue; - } - - // get the time - if (millisecondTime == null) { - msec = (long)event.sec * 1000L + (event.nsec / 1000000L); - millisecondTime = new Millisecond(new Date(msec)); - } - - // add the value to the time series - timeSeries.addOrUpdate(millisecondTime, value); - } catch (InvalidTypeException e) { - // just ignore this descriptor if there's a type mismatch - } - } - - for (OccurrenceDisplayDescriptor descriptor : occurrenceDescriptors) { - try { - // get the hashmap for this descriptor - HashMap<Integer, TimeSeries> map = mOcurrenceDescriptorSeriesMap.get(descriptor); - - // if it's not there yet, we create it. - if (map == null) { - map = new HashMap<Integer, TimeSeries>(); - mOcurrenceDescriptorSeriesMap.put(descriptor, map); - } - - // get the TimeSeries for this pid - TimeSeries timeSeries = map.get(event.pid); - - // if it doesn't exist yet, we create it. - if (timeSeries == null) { - String seriesLabel = getSeriesLabel(event, descriptor); - - String seriesFullName = String.format("[%1$s:%2$s]", - tagMap.get(descriptor.eventTag), seriesLabel); - - timeSeries = new TimeSeries(seriesFullName, Millisecond.class); - if (mMaximumChartItemAge != -1) { - timeSeries.setMaximumItemAge(mMaximumChartItemAge); - } - - getOccurrenceDataSet().addSeries(timeSeries); - - map.put(event.pid, timeSeries); - } - - // update the series - - // get the time - if (millisecondTime == null) { - msec = (long)event.sec * 1000L + (event.nsec / 1000000L); - millisecondTime = new Millisecond(new Date(msec)); - } - - // add the value to the time series - timeSeries.addOrUpdate(millisecondTime, 0); // the value is unused - } catch (InvalidTypeException e) { - // just ignore this descriptor if there's a type mismatch - } - } - - // go through all the series and remove old values. - if (msec != -1 && mMaximumChartItemAge != -1) { - Collection<HashMap<Integer, TimeSeries>> pidMapValues = - mValueDescriptorSeriesMap.values(); - - for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) { - Collection<TimeSeries> seriesCollection = pidMapValue.values(); - - for (TimeSeries timeSeries : seriesCollection) { - timeSeries.removeAgedItems(msec, true); - } - } - - pidMapValues = mOcurrenceDescriptorSeriesMap.values(); - for (HashMap<Integer, TimeSeries> pidMapValue : pidMapValues) { - Collection<TimeSeries> seriesCollection = pidMapValue.values(); - - for (TimeSeries timeSeries : seriesCollection) { - timeSeries.removeAgedItems(msec, true); - } - } - } - } - - /** - * Return the series label for this event. This only contains the pid information. - * @param event the {@link EventContainer} - * @param descriptor the {@link OccurrenceDisplayDescriptor} - * @return the series label. - * @throws InvalidTypeException - */ - private String getSeriesLabel(EventContainer event, OccurrenceDisplayDescriptor descriptor) - throws InvalidTypeException { - if (descriptor.seriesValueIndex != -1) { - if (descriptor.includePid == false) { - return event.getValueAsString(descriptor.seriesValueIndex); - } else { - return String.format("%1$s (%2$d)", - event.getValueAsString(descriptor.seriesValueIndex), event.pid); - } - } - - return Integer.toString(event.pid); - } - - /** * Fills a list with {@link OccurrenceDisplayDescriptor} (or a subclass of it) from another * list if they are configured to display the {@link EventContainer} - * @param event the event container + * + * @param event the event container * @param fullList the list with all the descriptors. - * @param outList the list to fill. + * @param outList the list to fill. */ @SuppressWarnings("unchecked") private void getDescriptors(EventContainer event, @@ -1348,200 +866,21 @@ final class EventDisplay { } } } - - /** - * Adds an {@link EventContainer} to the log. - * @param event the event. - * @param logParser the log parser. - */ - private void addToLog(EventContainer event, EventLogParser logParser) { - ScrollBar bar = mLogTable.getVerticalBar(); - boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb(); - - // get the date. - Calendar c = Calendar.getInstance(); - long msec = (long)event.sec * 1000L; - c.setTimeInMillis(msec); - - // convert the time into a string - String date = String.format("%1$tF %1$tT", c); - - String eventName = logParser.getTagMap().get(event.mTag); - String pidName = Integer.toString(event.pid); - - // get the value description - EventValueDescription[] valueDescription = logParser.getEventInfoMap().get(event.mTag); - if (valueDescription != null) { - for (int i = 0 ; i < valueDescription.length ; i++) { - EventValueDescription description = valueDescription[i]; - try { - String value = event.getValueAsString(i); - - logValue(date, pidName, eventName, description.getName(), value, - description.getEventValueType(), description.getValueType()); - } catch (InvalidTypeException e) { - logValue(date, pidName, eventName, description.getName(), e.getMessage(), - description.getEventValueType(), description.getValueType()); - } - } - - // scroll if needed, by showing the last item - if (scroll) { - int itemCount = mLogTable.getItemCount(); - if (itemCount > 0) { - mLogTable.showItem(mLogTable.getItem(itemCount-1)); - } - } - } - } - - /** - * Adds an {@link EventContainer} to the log. Only add the values/occurrences defined by - * the list of descriptors. If an event is configured to be displayed by value and occurrence, - * only the values are displayed (as they mark an event occurrence anyway). - * <p/>This method is only called when at least one of the descriptor list is non empty. - * @param event - * @param logParser - * @param valueDescriptors - * @param occurrenceDescriptors - */ - private void addToLog(EventContainer event, EventLogParser logParser, - ArrayList<ValueDisplayDescriptor> valueDescriptors, - ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) { - ScrollBar bar = mLogTable.getVerticalBar(); - boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb(); - - // get the date. - Calendar c = Calendar.getInstance(); - long msec = (long)event.sec * 1000L; - c.setTimeInMillis(msec); - - // convert the time into a string - String date = String.format("%1$tF %1$tT", c); - - String eventName = logParser.getTagMap().get(event.mTag); - String pidName = Integer.toString(event.pid); - - if (valueDescriptors.size() > 0) { - for (ValueDisplayDescriptor descriptor : valueDescriptors) { - logDescriptor(event, descriptor, date, pidName, eventName, logParser); - } - } else { - // we display the event. Since the StringBuilder contains the header (date, event name, - // pid) at this point, there isn't anything else to display. - } - - // scroll if needed, by showing the last item - if (scroll) { - int itemCount = mLogTable.getItemCount(); - if (itemCount > 0) { - mLogTable.showItem(mLogTable.getItem(itemCount-1)); - } - } - } - - /** - * Logs a value from an {@link EventContainer} as defined by the {@link ValueDisplayDescriptor}. - * @param event the EventContainer - * @param descriptor the ValueDisplayDescriptor defining which value to display. - * @param date the date of the event in a string. - * @param pidName - * @param eventName - * @param logParser - */ - private void logDescriptor(EventContainer event, ValueDisplayDescriptor descriptor, - String date, String pidName, String eventName, EventLogParser logParser) { - - String value; - try { - value = event.getValueAsString(descriptor.valueIndex); - } catch (InvalidTypeException e) { - value = e.getMessage(); - } - - EventValueDescription[] values = logParser.getEventInfoMap().get(event.mTag); - - EventValueDescription valueDescription = values[descriptor.valueIndex]; - - logValue(date, pidName, eventName, descriptor.valueName, value, - valueDescription.getEventValueType(), valueDescription.getValueType()); - } - - /** - * Logs a value in the ui. - * @param date - * @param pid - * @param event - * @param valueName - * @param value - * @param eventValueType - * @param valueType - */ - private void logValue(String date, String pid, String event, String valueName, - String value, EventValueType eventValueType, ValueType valueType) { - - TableItem item = new TableItem(mLogTable, SWT.NONE); - item.setText(0, date); - item.setText(1, pid); - item.setText(2, event); - item.setText(3, valueName); - item.setText(4, value); - - String type; - if (valueType != ValueType.NOT_APPLICABLE) { - type = String.format("%1$s, %2$s", eventValueType.toString(), valueType.toString()); - } else { - type = eventValueType.toString(); - } - - item.setText(5, type); - } /** - * Show the current value(s) of an {@link EventContainer}. The values to show are defined by - * the {@link ValueDisplayDescriptor}s and {@link OccurrenceDisplayDescriptor}s passed in the - * two lists. - * @param event - * @param logParser - * @param valueDescriptors - * @param occurrenceDescriptors - */ - private void showCurrent(EventContainer event, EventLogParser logParser, - ArrayList<ValueDisplayDescriptor> valueDescriptors, - ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) { - // TODO Auto-generated method stub - } - - // Values from data/etc/event-log-tags - final int EVENT_SYNC = 2720; - final int EVENT_TICKLE = 2742; - final int EVENT_SYNC_DETAILS = 2743; - - /** - * Filters the {@link EventContainer}, and fills two list of {@link ValueDisplayDescriptor} - * and {@link OccurrenceDisplayDescriptor} configured to display the event. + * Filters the {@link com.android.ddmlib.log.EventContainer}, and fills two list of {@link com.android.ddmuilib.log.event.EventDisplay.ValueDisplayDescriptor} + * and {@link com.android.ddmuilib.log.event.EventDisplay.OccurrenceDisplayDescriptor} configured to display the event. + * * @param event * @param valueDescriptors * @param occurrenceDescriptors * @return true if the event should be displayed. */ - private boolean filterEvent(EventContainer event, + + protected boolean filterEvent(EventContainer event, ArrayList<ValueDisplayDescriptor> valueDescriptors, ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) { - if (mDisplayType == DISPLAY_TYPE_LOG_ALL) { - return true; - } - - if (mDisplayType == DISPLAY_TYPE_SYNC || mDisplayType == DISPLAY_TYPE_SYNC_HIST) { - if (event.mTag == EVENT_SYNC || event.mTag == EVENT_TICKLE || - event.mTag == EVENT_SYNC_DETAILS) { - return true; - } else { - return false; - } - } - // test the pid first (if needed) if (mPidFiltering && mPidFilterList != null) { boolean found = false; @@ -1551,7 +890,7 @@ final class EventDisplay { break; } } - + if (found == false) { return false; } @@ -1569,7 +908,8 @@ final class EventDisplay { * Checks all the {@link ValueDisplayDescriptor} for similarity. * If all the event values are from the same tag, the method will return EVENT_CHECK_SAME_TAG. * If all the event/value are the same, the method will return EVENT_CHECK_SAME_VALUE - * @return + * + * @return flag as described above */ private int checkDescriptors() { if (mValueDescriptors.size() < 2) { @@ -1598,39 +938,8 @@ final class EventDisplay { if (index == -1) { return EVENT_CHECK_SAME_TAG; } - - return EVENT_CHECK_SAME_VALUE; - } - - /** - * Returns a meaningful chart title based on the value of {@link #mValueDescriptorCheck}. - * @param logParser the logParser. - * @return the chart title. - */ - private String getChartTitle(EventLogParser logParser) { - if (mValueDescriptors.size() > 0) { - String chartDesc = null; - switch (mValueDescriptorCheck) { - case EVENT_CHECK_SAME_TAG: - if (logParser != null) { - chartDesc = logParser.getTagMap().get(mValueDescriptors.get(0).eventTag); - } - break; - case EVENT_CHECK_SAME_VALUE: - if (logParser != null) { - chartDesc = String.format("%1$s / %2$s", - logParser.getTagMap().get(mValueDescriptors.get(0).eventTag), - mValueDescriptors.get(0).valueName); - } - break; - } - - if (chartDesc != null) { - return String.format("%1$s - %2$s", mName, chartDesc); - } - } - return mName; + return EVENT_CHECK_SAME_VALUE; } /** @@ -1642,17 +951,19 @@ final class EventDisplay { /** * Sets the time limit on the charts. + * * @param timeLimit the time limit in seconds. */ void setChartTimeLimit(long timeLimit) { mMaximumChartItemAge = timeLimit; } - + long getChartTimeLimit() { return mMaximumChartItemAge; } /** + * m * Resets the histogram width */ void resetHistWidth() { @@ -1661,150 +972,24 @@ final class EventDisplay { /** * Sets the histogram width + * * @param histWidth the width in hours */ void setHistWidth(long histWidth) { mHistWidth = histWidth; } - + long getHistWidth() { return mHistWidth; } - // Implementation of the Sync display - // TODO: DISPLAY_TYPE_LOG, DISPLAY_TYPE_GRAPH, and DISPLAY_TYPE_SYNC should be subclasses - // of EventDisplay.java - - private static final int CALENDAR = 0; - private static final int GMAIL = 1; - private static final int FEEDS = 2; - private static final int CONTACTS = 3; - private static final int ERRORS = 4; - private static final int NUM_AUTHS = (CONTACTS+1); - private static final String AUTH_NAMES[] = {"Calendar", "Gmail", "Feeds", "Contacts", "Errors"}; - private static final Color AUTH_COLORS[] = {Color.MAGENTA, Color.GREEN, Color.BLUE, Color.ORANGE, Color.RED}; - - // Information to graph for each authority - private TimePeriodValues mDatasetsSync[]; - private List<String> mTooltipsSync[]; - private CustomXYToolTipGenerator mTooltipGenerators[]; - private TimeSeries mDatasetsSyncTickle[]; - - // Dataset of error events to graph - private TimeSeries mDatasetError; - - /** - * Initialize the Plot and series data for the sync display. - */ - void initSyncDisplay() { - XYPlot xyPlot = mChart.getXYPlot(); - - XYBarRenderer br = new XYBarRenderer(); - mDatasetsSync = new TimePeriodValues[NUM_AUTHS]; - mTooltipsSync = new List[NUM_AUTHS]; - mTooltipGenerators = new CustomXYToolTipGenerator[NUM_AUTHS]; - mLastDetails = ""; - - TimePeriodValuesCollection tpvc = new TimePeriodValuesCollection(); - xyPlot.setDataset(tpvc); - xyPlot.setRenderer(0, br); - - XYLineAndShapeRenderer ls = new XYLineAndShapeRenderer(); - ls.setBaseLinesVisible(false); - mDatasetsSyncTickle = new TimeSeries[NUM_AUTHS]; - TimeSeriesCollection tsc = new TimeSeriesCollection(); - xyPlot.setDataset(1, tsc); - xyPlot.setRenderer(1, ls); - - mDatasetError = new TimeSeries("Errors", FixedMillisecond.class); - xyPlot.setDataset(2, new TimeSeriesCollection(mDatasetError)); - XYLineAndShapeRenderer errls = new XYLineAndShapeRenderer(); - errls.setBaseLinesVisible(false); - errls.setSeriesPaint(0, Color.RED); - xyPlot.setRenderer(2, errls); - - for (int i = 0; i < NUM_AUTHS; i++) { - br.setSeriesPaint(i, AUTH_COLORS[i]); - ls.setSeriesPaint(i, AUTH_COLORS[i]); - mDatasetsSync[i] = new TimePeriodValues(AUTH_NAMES[i]); - tpvc.addSeries(mDatasetsSync[i]); - mTooltipsSync[i] = new ArrayList<String>(); - mTooltipGenerators[i] = new CustomXYToolTipGenerator(); - br.setSeriesToolTipGenerator(i, mTooltipGenerators[i]); - mTooltipGenerators[i].addToolTipSeries(mTooltipsSync[i]); - - mDatasetsSyncTickle[i] = new TimeSeries(AUTH_NAMES[i] + " tickle", FixedMillisecond.class); - tsc.addSeries(mDatasetsSyncTickle[i]); - ls.setSeriesShape(i, ShapeUtilities.createUpTriangle(2.5f)); - } - } - - // State information while processing the event stream - private int mLastState; // 0 if event started, 1 if event stopped - private long mLastStartTime; // ms - private long mLastStopTime; //ms - private String mLastDetails; - private int mLastEvent; // server, poll, etc - - /** - * Updates the display with a new event. This is the main entry point for - * each event. This method has the logic to tie together the start event, - * stop event, and details event into one graph item. Note that the details - * can happen before or after the stop event. - * @param event The event - */ - private void updateSyncDisplay(EventContainer event) { - try { - if (event.mTag == EVENT_SYNC) { - int state = Integer.parseInt(event.getValueAsString(1)); - if (state == 0) { // start - mLastStartTime = (long)event.sec * 1000L + (event.nsec / 1000000L); - mLastState = 0; - mLastEvent = Integer.parseInt(event.getValueAsString(2)); - mLastDetails = ""; - } else if (state == 1) { // stop - if (mLastState == 0) { - mLastStopTime = (long)event.sec * 1000L + (event.nsec / 1000000L); - if (mLastStartTime == 0) { - // Log starts with a stop event - mLastStartTime = mLastStopTime; - } - addEvent(event); - mLastState = 1; - } - } - } else if (event.mTag == EVENT_TICKLE) { - int auth = getAuth(event.getValueAsString(0)); - if (auth >= 0) { - long msec = (long)event.sec * 1000L + (event.nsec / 1000000L); - mDatasetsSyncTickle[auth].addOrUpdate(new FixedMillisecond(msec), -1); - } - } else if (event.mTag == EVENT_SYNC_DETAILS) { - int auth = getAuth(event.getValueAsString(0)); - mLastDetails = event.getValueAsString(3); - if (mLastState != 0) { // Not inside event - long updateTime = (long)event.sec * 1000L + (event.nsec / 1000000L); - if (updateTime - mLastStopTime <= 250) { - // Got details within 250ms after event, so delete and re-insert - // Details later than 250ms (arbitrary) are discarded as probably - // unrelated. - int lastItem = mDatasetsSync[auth].getItemCount(); - mDatasetsSync[auth].delete(lastItem-1, lastItem-1); - mTooltipsSync[auth].remove(lastItem-1); - addEvent(event); - } - } - } - } catch (InvalidTypeException e) { - } - } - /** * Convert authority name to auth number. + * * @param authname "calendar", etc. * @return number series number associated with the authority */ - private int getAuth(String authname) throws InvalidTypeException { + protected int getAuth(String authname) throws InvalidTypeException { if ("calendar".equals(authname) || "cl".equals(authname)) { return CALENDAR; } else if ("contacts".equals(authname) || "cp".equals(authname)) { @@ -1821,283 +1006,4 @@ final class EventDisplay { throw new InvalidTypeException("Unknown authname " + authname); } } - - /** - * Generate the height for an event. - * Height is somewhat arbitrarily the count of "things" that happened - * during the sync. - * When network traffic measurements are available, code should be modified - * to use that instead. - * @param details The details string associated with the event - * @return The height in arbirary units (0-100) - */ - private int getHeightFromDetails(String details) { - if (details == null) { - return 1; // Arbitrary - } - int total = 0; - String parts[] = details.split("[a-zA-Z]"); - for (String part : parts) { - if ("".equals(part)) continue; - total += Integer.parseInt(part); - } - if (total == 0) { - total = 1; - } - return total; - } - - /** - * Generates the tooltips text for an event. - * This method decodes the cryptic details string. - * @param auth The authority associated with the event - * @param details The details string - * @param eventSource server, poll, etc. - * @return The text to display in the tooltips - */ - private String getTextFromDetails(int auth, String details, int eventSource) { - - StringBuffer sb = new StringBuffer(); - sb.append(AUTH_NAMES[auth]).append(": \n"); - - Scanner scanner = new Scanner(details); - Pattern charPat = Pattern.compile("[a-zA-Z]"); - Pattern numPat = Pattern.compile("[0-9]+"); - while (scanner.hasNext()) { - String key = scanner.findInLine(charPat); - int val = Integer.parseInt(scanner.findInLine(numPat)); - if (auth == GMAIL && "M".equals(key)) { - sb.append("messages from server: ").append(val).append("\n"); - } else if (auth == GMAIL && "L".equals(key)) { - sb.append("labels from server: ").append(val).append("\n"); - } else if (auth == GMAIL && "C".equals(key)) { - sb.append("check conversation requests from server: ").append(val).append("\n"); - } else if (auth == GMAIL && "A".equals(key)) { - sb.append("attachments from server: ").append(val).append("\n"); - } else if (auth == GMAIL && "U".equals(key)) { - sb.append("op updates from server: ").append(val).append("\n"); - } else if (auth == GMAIL && "u".equals(key)) { - sb.append("op updates to server: ").append(val).append("\n"); - } else if (auth == GMAIL && "S".equals(key)) { - sb.append("send/receive cycles: ").append(val).append("\n"); - } else if ("Q".equals(key)) { - sb.append("queries to server: ").append(val).append("\n"); - } else if ("E".equals(key)) { - sb.append("entries from server: ").append(val).append("\n"); - } else if ("u".equals(key)) { - sb.append("updates from client: ").append(val).append("\n"); - } else if ("i".equals(key)) { - sb.append("inserts from client: ").append(val).append("\n"); - } else if ("d".equals(key)) { - sb.append("deletes from client: ").append(val).append("\n"); - } else if ("f".equals(key)) { - sb.append("full sync requested\n"); - } else if ("r".equals(key)) { - sb.append("partial sync unavailable\n"); - } else if ("X".equals(key)) { - sb.append("hard error\n"); - } else if ("e".equals(key)) { - sb.append("number of parse exceptions: ").append(val).append("\n"); - } else if ("c".equals(key)) { - sb.append("number of conflicts: ").append(val).append("\n"); - } else if ("a".equals(key)) { - sb.append("number of auth exceptions: ").append(val).append("\n"); - } else if ("D".equals(key)) { - sb.append("too many deletions\n"); - } else if ("R".equals(key)) { - sb.append("too many retries: ").append(val).append("\n"); - } else if ("b".equals(key)) { - sb.append("database error\n"); - } else if ("x".equals(key)) { - sb.append("soft error\n"); - } else if ("l".equals(key)) { - sb.append("sync already in progress\n"); - } else if ("I".equals(key)) { - sb.append("io exception\n"); - } else if (auth == CONTACTS && "p".equals(key)) { - sb.append("photos uploaded from client: ").append(val).append("\n"); - } else if (auth == CONTACTS && "P".equals(key)) { - sb.append("photos downloaded from server: ").append(val).append("\n"); - } else if (auth == CALENDAR && "F".equals(key)) { - sb.append("server refresh\n"); - } else if (auth == CALENDAR && "s".equals(key)) { - sb.append("server diffs fetched\n"); - } else { - sb.append(key).append("=").append(val); - } - } - if (eventSource == 0) { - sb.append("(server)"); - } else if (eventSource == 1) { - sb.append("(local)"); - } else if (eventSource == 2) { - sb.append("(poll)"); - } else if (eventSource == 3) { - sb.append("(user)"); - } - return sb.toString(); - } - - /** - * Helper to add an event to the data series. - * Also updates error series if appropriate (x or X in details). - * @param event The event - */ - private void addEvent(EventContainer event) { - try { - int auth = getAuth(event.getValueAsString(0)); - double height = getHeightFromDetails(mLastDetails); - height = height / (mLastStopTime - mLastStartTime + 1) * 10000; - if (height > 30) { - height = 30; - } - mDatasetsSync[auth].add(new SimpleTimePeriod(mLastStartTime, mLastStopTime), height); - mTooltipsSync[auth].add(getTextFromDetails(auth, mLastDetails, - mLastEvent)); - mTooltipGenerators[auth].addToolTipSeries(mTooltipsSync[auth]); - if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) { - long msec = (long)event.sec * 1000L + (event.nsec / 1000000L); - mDatasetError.addOrUpdate(new FixedMillisecond(msec), -1); - } - } catch (InvalidTypeException e) { - e.printStackTrace(); - } - } - - // Implementation of the Sync Histogram display - - // Information to graph for each authority - private TimePeriodValues mDatasetsSyncHist[]; - - /** - * Initialize the Plot and series data for the sync display. - */ - void initSyncHistDisplay() { - XYPlot xyPlot = mChart.getXYPlot(); - - AbstractXYItemRenderer br = new XYBarRenderer(); - mDatasetsSyncHist = new TimePeriodValues[NUM_AUTHS+1]; - mLastDetails = ""; - mTimePeriodMap = new HashMap[NUM_AUTHS + 1]; - - TimePeriodValuesCollection tpvc = new TimePeriodValuesCollection(); - xyPlot.setDataset(tpvc); - xyPlot.setRenderer(br); - - for (int i = 0; i < NUM_AUTHS + 1; i++) { - br.setSeriesPaint(i, AUTH_COLORS[i]); - mDatasetsSyncHist[i] = new TimePeriodValues(AUTH_NAMES[i]); - tpvc.addSeries(mDatasetsSyncHist[i]); - mTimePeriodMap[i] = new HashMap<SimpleTimePeriod, Integer>(); - - } - } - - /** - * Updates the display with a new event. This is the main entry point for - * each event. This method has the logic to tie together the start event, - * stop event, and details event into one graph item. Note that the details - * can happen before or after the stop event. - * @param event The event - */ - private void updateSyncHistDisplay(EventContainer event) { - try { - if (event.mTag == EVENT_SYNC) { - int state = Integer.parseInt(event.getValueAsString(1)); - if (state == 0) { // start - mLastStartTime = (long)event.sec * 1000L + (event.nsec / 1000000L); - mLastState = 0; - mLastEvent = Integer.parseInt(event.getValueAsString(2)); - mLastDetails = ""; - } else if (state == 1) { // stop - if (mLastState == 0) { - mLastStopTime = (long)event.sec * 1000L + (event.nsec / 1000000L); - if (mLastStartTime == 0) { - // Log starts with a stop event - mLastStartTime = mLastStopTime; - } - int auth = getAuth(event.getValueAsString(0)); - if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) { - auth = ERRORS; - } - double delta = (mLastStopTime - mLastStartTime) * 100. / 1000 / 3600; // Percent of hour - addHistEvent(event, auth, delta); - mLastState = 1; - } - } - } else if (event.mTag == EVENT_SYNC_DETAILS) { - int auth = getAuth(event.getValueAsString(0)); - mLastDetails = event.getValueAsString(3); - if (mLastState != 0) { // Not inside event - long updateTime = (long)event.sec * 1000L + (event.nsec / 1000000L); - if (updateTime - mLastStopTime <= 250) { - // Got details within 250ms after event, so delete and re-insert - // Details later than 250ms (arbitrary) are discarded as probably - // unrelated. - //int lastItem = mDatasetsSync[auth].getItemCount(); - //addHistEvent(event); - if (mLastDetails.indexOf('x') >= 0 || mLastDetails.indexOf('X') >= 0) { - // Item turns out to be in error, so transfer time from old auth to error. - - double delta = (mLastStopTime - mLastStartTime) * 100. / 1000 / 3600; // Percent of hour - addHistEvent(event, auth, -delta); - addHistEvent(event, ERRORS, delta); - } - } - } - } - } catch (InvalidTypeException e) { - } - } - - /** - * Helper to add an event to the data series. - * Also updates error series if appropriate (x or X in details). - * @param event The event - * @param auth - * @param value - */ - private void addHistEvent(EventContainer event, int auth, double value) { - SimpleTimePeriod hour = getTimePeriod(mLastStopTime, mHistWidth); - - // Loop over all datasets to do the stacking. - for (int i = auth; i <= ERRORS; i++) { - addToPeriod(mDatasetsSyncHist, i, hour, value); - } - } - - Map<SimpleTimePeriod, Integer> mTimePeriodMap[]; - - private void addToPeriod(TimePeriodValues tpv[], int auth, SimpleTimePeriod period, double value) { - int index; - if (mTimePeriodMap[auth].containsKey(period)) { - index = mTimePeriodMap[auth].get(period); - double oldValue = tpv[auth].getValue(index).doubleValue(); - tpv[auth].update(index, oldValue + value); - } else { - index = tpv[auth].getItemCount(); - mTimePeriodMap[auth].put(period, index); - tpv[auth].add(period, value); - } - } - - /** - * Creates a multiple-hour time period for the histogram. - * @param time Time in milliseconds. - * @param numHoursWide: should divide into a day. - * @return SimpleTimePeriod covering the number of hours and containing time. - */ - private SimpleTimePeriod getTimePeriod(long time, long numHoursWide) { - Date date = new Date(time); - TimeZone zone = RegularTimePeriod.DEFAULT_TIME_ZONE; - Calendar calendar = Calendar.getInstance(zone); - calendar.setTime(date); - long hoursOfYear = calendar.get(Calendar.HOUR_OF_DAY) + calendar.get(Calendar.DAY_OF_YEAR) * 24; - int year = calendar.get(Calendar.YEAR); - hoursOfYear = (hoursOfYear / numHoursWide) * numHoursWide; - calendar.clear(); - calendar.set(year, 0, 1, 0, 0); // Jan 1 - long start = calendar.getTimeInMillis() + hoursOfYear * 3600 * 1000; - return new SimpleTimePeriod(start, start + numHoursWide * 3600 * 1000); - } } diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplayOptions.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplayOptions.java index 94f04d7..b9daa41 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplayOptions.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventDisplayOptions.java @@ -23,7 +23,6 @@ import com.android.ddmuilib.DdmUiPreferences; import com.android.ddmuilib.IImageLoader; import com.android.ddmuilib.log.event.EventDisplay.OccurrenceDisplayDescriptor; import com.android.ddmuilib.log.event.EventDisplay.ValueDisplayDescriptor; - import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; @@ -444,10 +443,13 @@ class EventDisplayOptions extends Dialog { @Override public void widgetSelected(SelectionEvent e) { EventDisplay eventDisplay = getCurrentEventDisplay(); - if (eventDisplay != null) { + if (eventDisplay != null && eventDisplay.getDisplayType() != mDisplayTypeCombo.getSelectionIndex()) { + /* Replace the EventDisplay object with a different subclass */ setModified(); - eventDisplay.setDisplayType(mDisplayTypeCombo.getSelectionIndex()); - fillUiWith(eventDisplay); + String name = eventDisplay.getName(); + EventDisplay newEventDisplay = EventDisplay.eventDisplayFactory(mDisplayTypeCombo.getSelectionIndex(), name); + setCurrentEventDisplay(newEventDisplay); + fillUiWith(newEventDisplay); } } }); @@ -693,7 +695,7 @@ class EventDisplayOptions extends Dialog { private void duplicateEventDisplay(ArrayList<EventDisplay> displayList) { for (EventDisplay eventDisplay : displayList) { - mDisplayList.add(new EventDisplay(eventDisplay)); + mDisplayList.add(EventDisplay.clone(eventDisplay)); } } @@ -744,7 +746,7 @@ class EventDisplayOptions extends Dialog { String name = String.format("display %1$d", count + 1); - EventDisplay eventDisplay = new EventDisplay(name); + EventDisplay eventDisplay = EventDisplay.eventDisplayFactory(0 /* type*/, name); mDisplayList.add(eventDisplay); mEventDisplayList.add(name); @@ -779,6 +781,13 @@ class EventDisplayOptions extends Dialog { return null; } + + private void setCurrentEventDisplay(EventDisplay eventDisplay) { + int selection = mEventDisplayList.getSelectionIndex(); + if (selection != -1) { + mDisplayList.set(selection, eventDisplay); + } + } private void handleEventDisplaySelection() { EventDisplay eventDisplay = getCurrentEventDisplay(); diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventLogImporter.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventLogImporter.java index 2ace78a..a1303f6 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventLogImporter.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/log/event/EventLogImporter.java @@ -38,7 +38,7 @@ public class EventLogImporter { if (top == null) { throw new FileNotFoundException(); } - final String tagFile = top + "/data/etc/event-log-tags"; + final String tagFile = top + "/system/core/logcat/event-log-tags"; BufferedReader tagReader = new BufferedReader( new InputStreamReader(new FileInputStream(tagFile))); BufferedReader eventReader = new BufferedReader( diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java index 6743246..82bcea8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java @@ -181,7 +181,7 @@ public class ApkBuilder extends BaseBuilder { return mMakeFinalPackage; } } - + /** * {@link IZipEntryFilter} to filter out everything that is not a standard java resources. * <p/>Used in {@link SignedJarBuilder#writeZip(java.io.InputStream, IZipEntryFilter)} when @@ -215,6 +215,7 @@ public class ApkBuilder extends BaseBuilder { // First thing we do is go through the resource delta to not // lose it if we have to abort the build for any reason. + ApkDeltaVisitor dv = null; if (kind == FULL_BUILD) { AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, Messages.Start_Full_Apk_Build); @@ -233,22 +234,13 @@ public class ApkBuilder extends BaseBuilder { mConvertToDex = true; mBuildFinalPackage = true; } else { - ApkDeltaVisitor dv = new ApkDeltaVisitor(this, sourceList, outputFolder); + dv = new ApkDeltaVisitor(this, sourceList, outputFolder); delta.accept(dv); // save the state mPackageResources |= dv.getPackageResources(); mConvertToDex |= dv.getConvertToDex(); mBuildFinalPackage |= dv.getMakeFinalPackage(); - - if (dv.mXmlError) { - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.Xml_Error); - - // if there was some XML errors, we just return w/o doing - // anything since we've put some markers in the files anyway - return referencedProjects; - } } // also go through the delta for all the referenced projects, until we are forced to @@ -258,13 +250,13 @@ public class ApkBuilder extends BaseBuilder { IJavaProject referencedJavaProject = referencedJavaProjects[i]; delta = getDelta(referencedJavaProject.getProject()); if (delta != null) { - ReferencedProjectDeltaVisitor dv = new ReferencedProjectDeltaVisitor( + ReferencedProjectDeltaVisitor refProjectDv = new ReferencedProjectDeltaVisitor( referencedJavaProject); - delta.accept(dv); + delta.accept(refProjectDv); // save the state - mConvertToDex |= dv.needDexConvertion(); - mBuildFinalPackage |= dv.needMakeFinalPackage(); + mConvertToDex |= refProjectDv.needDexConvertion(); + mBuildFinalPackage |= refProjectDv.needMakeFinalPackage(); } } } @@ -307,29 +299,14 @@ public class ApkBuilder extends BaseBuilder { // At this point, we can abort the build if we have to, as we have computed // our resource delta and stored the result. + abortOnBadSetup(javaProject); - // check if we have finished loading the SDK. - if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) { - // we exit silently - return referencedProjects; - } - - // Now check the compiler compliance level, not displaying the error - // message since this is not the first builder. - if (ProjectHelper.checkCompilerCompliance(getProject()) - != ProjectHelper.COMPILER_COMPLIANCE_OK) { - return referencedProjects; - } - - // now check if the project has problem marker already - if (ProjectHelper.hasError(project, true)) { - // we found a marker with error severity: we abort the build. - // Since this is going to happen every time we save a file while - // errors are remaining, we do not force the display of the console, which - // would, in most cases, show on top of the Problem view (which is more - // important in that case). + if (dv != null && dv.mXmlError) { AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.Project_Has_Errors); + Messages.Xml_Error); + + // if there was some XML errors, we just return w/o doing + // anything since we've put some markers in the files anyway return referencedProjects; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java index 534c123..04e9fbf 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java @@ -19,10 +19,13 @@ package com.android.ide.eclipse.adt.build; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.sdk.LoadStatus; +import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.common.project.XmlErrorHandler; import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener; +import com.android.sdklib.IAndroidTarget; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -34,6 +37,8 @@ import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; @@ -841,4 +846,53 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { return oslibraryList.toArray(new String[oslibraryList.size()]); } + + /** + * Aborts the build if the SDK/project setups are broken. This does not + * display any errors. + * + * @param javaProject The {@link IJavaProject} being compiled. + * @throws CoreException + */ + protected final void abortOnBadSetup(IJavaProject javaProject) throws CoreException { + // check if we have finished loading the SDK. + if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) { + // we exit silently + stopBuild("SDK is not loaded yet"); + } + + // check the compiler compliance level. + if (ProjectHelper.checkCompilerCompliance(getProject()) != + ProjectHelper.COMPILER_COMPLIANCE_OK) { + // we exit silently + stopBuild(Messages.Compiler_Compliance_Error); + } + + // Check that the SDK directory has been setup. + String osSdkFolder = AdtPlugin.getOsSdkFolder(); + + if (osSdkFolder == null || osSdkFolder.length() == 0) { + stopBuild(Messages.No_SDK_Setup_Error); + } + + IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(javaProject.getProject()); + if (projectTarget == null) { + // no target. error has been output by the container initializer: + // exit silently. + stopBuild("Project has no target"); + } + } + + /** + * Throws an exception to cancel the build. + * + * @param error the error message + * @param args the printf-style arguments to the error message. + * @throws CoreException + */ + protected final void stopBuild(String error, Object... args) throws CoreException { + throw new CoreException(new Status(IStatus.CANCEL, AdtPlugin.PLUGIN_ID, + String.format(error, args))); + } + } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java index 9fc4348..fd4d772 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java @@ -20,7 +20,6 @@ import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.project.FixLaunchConfig; import com.android.ide.eclipse.adt.project.ProjectHelper; -import com.android.ide.eclipse.adt.sdk.LoadStatus; import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.AndroidManifestHelper; @@ -224,7 +223,7 @@ public class PreCompilerBuilder extends BaseBuilder { public PreCompilerBuilder() { super(); } - + // build() returns a list of project from which this project depends for future compilation. @SuppressWarnings("unchecked") //$NON-NLS-1$ @Override @@ -274,15 +273,6 @@ public class PreCompilerBuilder extends BaseBuilder { mergeAidlFileModifications(dv.getAidlToCompile(), dv.getAidlToRemove()); } - - // if there was some XML errors, we just return w/o doing - // anything since we've put some markers in the files anyway. - if (dv.mXmlError) { - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.Xml_Error); - - return null; - } // get the java package from the visitor javaPackage = dv.getManifestPackage(); @@ -295,39 +285,19 @@ public class PreCompilerBuilder extends BaseBuilder { // At this point we have stored what needs to be build, so we can // do some high level test and abort if needed. - - // check if we have finished loading the SDK. - if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) { - // we exit silently - return null; - } - - // check the compiler compliance level, not displaying the error message - // since this is not the first builder. - if (ProjectHelper.checkCompilerCompliance(getProject()) - != ProjectHelper.COMPILER_COMPLIANCE_OK) { + abortOnBadSetup(javaProject); + + // if there was some XML errors, we just return w/o doing + // anything since we've put some markers in the files anyway. + if (dv != null && dv.mXmlError) { AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.Compiler_Compliance_Error); - return null; - } - - // Check that the SDK directory has been setup. - String osSdkFolder = AdtPlugin.getOsSdkFolder(); + Messages.Xml_Error); - if (osSdkFolder == null || osSdkFolder.length() == 0) { - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.No_SDK_Setup_Error); - markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error, - IMarker.SEVERITY_ERROR); - return null; - } - - IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project); - if (projectTarget == null) { - // no target. error has been output by the container initializer: exit silently. - return null; + // This interrupts the build. The next builders will not run. + stopBuild(Messages.Xml_Error); } + // get the manifest file IFile manifest = AndroidManifestHelper.getManifest(project); @@ -336,7 +306,9 @@ public class PreCompilerBuilder extends BaseBuilder { AndroidConstants.FN_ANDROID_MANIFEST); AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); - return null; + + // This interrupts the build. The next builders will not run. + stopBuild(msg); } // lets check the XML of the manifest first, if that hasn't been done by the @@ -353,7 +325,9 @@ public class PreCompilerBuilder extends BaseBuilder { String msg = String.format(Messages.s_Contains_Xml_Error, AndroidConstants.FN_ANDROID_MANIFEST); AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); - return null; + + // This interrupts the build. The next builders will not run. + stopBuild(msg); } // get the java package from the parser @@ -366,7 +340,9 @@ public class PreCompilerBuilder extends BaseBuilder { AndroidConstants.FN_ANDROID_MANIFEST); AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); - return null; + + // This interrupts the build. The next builders will not run. + stopBuild(msg); } // at this point we have the java package. We need to make sure it's not a different package @@ -409,7 +385,8 @@ public class PreCompilerBuilder extends BaseBuilder { AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, message); // abort - return null; + // This interrupts the build. The next builders will not run. + stopBuild(message); } @@ -430,6 +407,8 @@ public class PreCompilerBuilder extends BaseBuilder { String osResPath = resLocation.toOSString(); String osManifestPath = manifestLocation.toOSString(); + IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project); + // remove the aapt markers removeMarkersFromFile(manifest, AndroidConstants.MARKER_AAPT); removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT); @@ -517,20 +496,25 @@ public class PreCompilerBuilder extends BaseBuilder { Messages.AAPT_Error); // abort if exec failed. - return null; + // This interrupts the build. The next builders will not run. + stopBuild(Messages.AAPT_Error); } } catch (IOException e1) { // something happen while executing the process, // mark the project and exit String msg = String.format(Messages.AAPT_Exec_Error, array.get(0)); markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); - return null; + + // This interrupts the build. The next builders will not run. + stopBuild(msg); } catch (InterruptedException e) { // we got interrupted waiting for the process to end... // mark the project and exit String msg = String.format(Messages.AAPT_Exec_Error, array.get(0)); markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); - return null; + + // This interrupts the build. The next builders will not run. + stopBuild(msg); } // if the return code was OK, we refresh the folder that diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java index 1219aac..1e7b77a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java @@ -19,16 +19,20 @@ package com.android.ide.eclipse.adt.build; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.sdk.LoadStatus; +import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.BaseProjectHelper; +import com.android.sdklib.IAndroidTarget; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; import java.util.Map; @@ -36,7 +40,7 @@ import java.util.Map; * Resource manager builder whose only purpose is to refresh the resource folder * so that the other builder use an up to date version. */ -public class ResourceManagerBuilder extends IncrementalProjectBuilder { +public class ResourceManagerBuilder extends BaseBuilder { public static final String ID = "com.android.ide.eclipse.adt.ResourceManagerBuilder"; //$NON-NLS-1$ @@ -72,6 +76,38 @@ public class ResourceManagerBuilder extends IncrementalProjectBuilder { BaseProjectHelper.addMarker(project, AdtConstants.MARKER_ADT, errorMessage, IMarker.SEVERITY_ERROR); AdtPlugin.printErrorToConsole(project, errorMessage); + + // interrupt the build. The next builders will not run. + stopBuild(errorMessage); + } + + // Check that the SDK directory has been setup. + String osSdkFolder = AdtPlugin.getOsSdkFolder(); + + if (osSdkFolder == null || osSdkFolder.length() == 0) { + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, + Messages.No_SDK_Setup_Error); + markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error, + IMarker.SEVERITY_ERROR); + + // This interrupts the build. The next builders will not run. + stopBuild(Messages.No_SDK_Setup_Error); + } + + // check if we have finished loading the SDK. + IJavaProject javaProject = JavaCore.create(project); + if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) { + // we exit silently + // This interrupts the build. The next builders will not run. + stopBuild("SDK is not loaded yet"); + } + + // check the project has a target + IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project); + if (projectTarget == null) { + // no target. marker has been set by the container initializer: exit silently. + // This interrupts the build. The next builders will not run. + stopBuild("Project has no target"); } // Check the preference to be sure we are supposed to refresh diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java index 2b7d01d..87f902a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java @@ -35,8 +35,8 @@ import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.project.AndroidManifestHelper; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkManager; -import com.android.sdklib.vm.VmManager; -import com.android.sdklib.vm.VmManager.VmInfo; +import com.android.sdklib.avd.AvdManager; +import com.android.sdklib.avd.AvdManager.AvdInfo; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; @@ -74,7 +74,7 @@ import java.util.regex.Pattern; public final class AndroidLaunchController implements IDebugBridgeChangeListener, IDeviceChangeListener, IClientChangeListener { - private static final String FLAG_VM = "-vm"; //$NON-NLS-1$ + private static final String FLAG_AVD = "-avd"; //$NON-NLS-1$ private static final String FLAG_NETDELAY = "-netdelay"; //$NON-NLS-1$ private static final String FLAG_NETSPEED = "-netspeed"; //$NON-NLS-1$ private static final String FLAG_WIPE_DATA = "-wipe-data"; //$NON-NLS-1$ @@ -228,9 +228,9 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener public boolean mNoBootAnim = LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM; /** - * Vm Name. + * AVD Name. */ - public String mVmName = null; + public String mAvdName = null; public String mNetworkSpeed = EmulatorConfigTab.getSpeed( LaunchConfigDelegate.DEFAULT_SPEED); @@ -262,7 +262,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } try { - mVmName = config.getAttribute(LaunchConfigDelegate.ATTR_VM_NAME, mVmName); + mAvdName = config.getAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, mAvdName); } catch (CoreException e) { } @@ -531,8 +531,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener wc.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, LaunchConfigDelegate.DEFAULT_TARGET_MODE); - // default VM: None - wc.setAttribute(LaunchConfigDelegate.ATTR_VM_NAME, (String)null); + // default AVD: None + wc.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String)null); // set the default network speed wc.setAttribute(LaunchConfigDelegate.ATTR_SPEED, @@ -629,12 +629,12 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener // get the SDK Sdk currentSdk = Sdk.getCurrent(); - VmManager vmManager = currentSdk.getVmManager(); + AvdManager avdManager = currentSdk.getAvdManager(); // get the project target final IAndroidTarget projectTarget = currentSdk.getTarget(project); - // FIXME: check errors on missing sdk, vm manager, or project target. + // FIXME: check errors on missing sdk, AVD manager, or project target. // device chooser response object. final DeviceChooserResponse response = new DeviceChooserResponse(); @@ -644,81 +644,81 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener * - Manually Mode * Always display a UI that lets a user see the current running emulators/devices. * The UI must show which devices are compatibles, and allow launching new emulators - * with compatible (and not yet running) VM. + * with compatible (and not yet running) AVD. * - Automatic Way - * * Preferred VM set. - * If Preferred VM is not running: launch it. - * Launch the application on the preferred VM. - * * No preferred VM. + * * Preferred AVD set. + * If Preferred AVD is not running: launch it. + * Launch the application on the preferred AVD. + * * No preferred AVD. * Count the number of compatible emulators/devices. * If != 1, display a UI similar to manual mode. - * If == 1, launch the application on this VM/device. + * If == 1, launch the application on this AVD/device. */ if (config.mTargetMode == AndroidLaunchConfiguration.AUTO_TARGET_MODE) { // if we are in automatic target mode, we need to find the current devices Device[] devices = AndroidDebugBridge.getBridge().getDevices(); - // first check if we have a preferred VM name, and if it actually exists, and is valid + // first check if we have a preferred AVD name, and if it actually exists, and is valid // (ie able to run the project). - // We need to check this in case the VM was recreated with a different target that is + // We need to check this in case the AVD was recreated with a different target that is // not compatible. - VmInfo preferredVm = null; - if (config.mVmName != null) { - preferredVm = vmManager.getVm(config.mVmName); - if (projectTarget.isCompatibleBaseFor(preferredVm.getTarget()) == false) { - preferredVm = null; + AvdInfo preferredAvd = null; + if (config.mAvdName != null) { + preferredAvd = avdManager.getAvd(config.mAvdName); + if (projectTarget.isCompatibleBaseFor(preferredAvd.getTarget()) == false) { + preferredAvd = null; AdtPlugin.printErrorToConsole(project, String.format( - "Preferred VM '%1$s' is not compatible with the project target '%2$s'. Looking for a compatible VM...", - config.mVmName, projectTarget.getName())); + "Preferred AVD '%1$s' is not compatible with the project target '%2$s'. Looking for a compatible AVD...", + config.mAvdName, projectTarget.getName())); } } - if (preferredVm != null) { + if (preferredAvd != null) { // look for a matching device for (Device d : devices) { - String deviceVm = d.getVmName(); - if (deviceVm != null && deviceVm.equals(config.mVmName)) { + String deviceAvd = d.getAvdName(); + if (deviceAvd != null && deviceAvd.equals(config.mAvdName)) { response.mustContinue = true; response.mustLaunchEmulator = false; response.deviceToUse = d; AdtPlugin.printToConsole(project, String.format( - "Automatic Target Mode: Preferred VM '%1$s' is available on emulator '%2$s'", - config.mVmName, d)); + "Automatic Target Mode: Preferred AVD '%1$s' is available on emulator '%2$s'", + config.mAvdName, d)); continueLaunch(response, project, launch, launchInfo, config); return; } } - // at this point we have a valid preferred VM that is not running. + // at this point we have a valid preferred AVD that is not running. // We need to start it. response.mustContinue = true; response.mustLaunchEmulator = true; - response.vmToLaunch = preferredVm; + response.avdToLaunch = preferredAvd; AdtPlugin.printToConsole(project, String.format( - "Automatic Target Mode: Preferred VM '%1$s' is not available. Launching new emulator.", - config.mVmName)); + "Automatic Target Mode: Preferred AVD '%1$s' is not available. Launching new emulator.", + config.mAvdName)); continueLaunch(response, project, launch, launchInfo, config); return; } - // no (valid) preferred VM? look for one. - HashMap<Device, VmInfo> compatibleRunningVms = new HashMap<Device, VmInfo>(); + // no (valid) preferred AVD? look for one. + HashMap<Device, AvdInfo> compatibleRunningAvds = new HashMap<Device, AvdInfo>(); boolean hasDevice = false; // if there's 1+ device running, we may force manual mode, // as we cannot always detect proper compatibility with // devices. This is the case if the project target is not // a standard platform for (Device d : devices) { - String deviceVm = d.getVmName(); - if (deviceVm != null) { // physical devices return null. - VmInfo info = vmManager.getVm(deviceVm); + String deviceAvd = d.getAvdName(); + if (deviceAvd != null) { // physical devices return null. + AvdInfo info = avdManager.getAvd(deviceAvd); if (info != null && projectTarget.isCompatibleBaseFor(info.getTarget())) { - compatibleRunningVms.put(d, info); + compatibleRunningAvds.put(d, info); } } else { if (projectTarget.isPlatform()) { // means this can run on any device as long @@ -728,7 +728,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener int apiNumber = Integer.parseInt(apiString); if (apiNumber >= projectTarget.getApiVersionNumber()) { // device is compatible with project - compatibleRunningVms.put(d, null); + compatibleRunningAvds.put(d, null); continue; } } catch (NumberFormatException e) { @@ -741,54 +741,54 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener // depending on the number of devices, we'll simulate an automatic choice // from the device chooser or simply show up the device chooser. - if (hasDevice == false && compatibleRunningVms.size() == 0) { + if (hasDevice == false && compatibleRunningAvds.size() == 0) { // if zero emulators/devices, we launch an emulator. - // We need to figure out which VM first. + // We need to figure out which AVD first. - // we are going to take the closest VM. ie a compatible VM that has the API level + // we are going to take the closest AVD. ie a compatible AVD that has the API level // closest to the project target. - VmInfo[] vms = vmManager.getVms(); - VmInfo defaultVm = null; - for (VmInfo vm : vms) { - if (projectTarget.isCompatibleBaseFor(vm.getTarget())) { - if (defaultVm == null || - vm.getTarget().getApiVersionNumber() < - defaultVm.getTarget().getApiVersionNumber()) { - defaultVm = vm; + AvdInfo[] avds = avdManager.getAvds(); + AvdInfo defaultAvd = null; + for (AvdInfo avd : avds) { + if (projectTarget.isCompatibleBaseFor(avd.getTarget())) { + if (defaultAvd == null || + avd.getTarget().getApiVersionNumber() < + defaultAvd.getTarget().getApiVersionNumber()) { + defaultAvd = avd; } } } - if (defaultVm != null) { + if (defaultAvd != null) { response.mustContinue = true; response.mustLaunchEmulator = true; - response.vmToLaunch = defaultVm; + response.avdToLaunch = defaultAvd; AdtPlugin.printToConsole(project, String.format( - "Automatic Target Mode: launching new emulator with compatible VM '%1$s'", - defaultVm.getName())); + "Automatic Target Mode: launching new emulator with compatible AVD '%1$s'", + defaultAvd.getName())); continueLaunch(response, project, launch, launchInfo, config); return; } else { - // FIXME: ask the user if he wants to create a VM. - // we found no compatible VM. + // FIXME: ask the user if he wants to create a AVD. + // we found no compatible AVD. AdtPlugin.printErrorToConsole(project, String.format( - "Failed to find a VM compatible with target '%1$s'. Launch aborted.", + "Failed to find a AVD compatible with target '%1$s'. Launch aborted.", projectTarget.getName())); launch.stopLaunch(); return; } - } else if (hasDevice == false && compatibleRunningVms.size() == 1) { - Entry<Device, VmInfo> e = compatibleRunningVms.entrySet().iterator().next(); + } else if (hasDevice == false && compatibleRunningAvds.size() == 1) { + Entry<Device, AvdInfo> e = compatibleRunningAvds.entrySet().iterator().next(); response.mustContinue = true; response.mustLaunchEmulator = false; response.deviceToUse = e.getKey(); - // get the VmInfo, if null, the device is a physical device. - VmInfo vmInfo = e.getValue(); - if (vmInfo != null) { - message = String.format("Automatic Target Mode: using existing emulator '%1$s' running compatible VM '%2$s'", + // get the AvdInfo, if null, the device is a physical device. + AvdInfo avdInfo = e.getValue(); + if (avdInfo != null) { + message = String.format("Automatic Target Mode: using existing emulator '%1$s' running compatible AVD '%2$s'", response.deviceToUse, e.getValue().getName()); } else { message = String.format("Automatic Target Mode: using device '%1$s'", @@ -801,7 +801,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } // if more than one device, we'll bring up the DeviceChooser dialog below. - if (compatibleRunningVms.size() >= 2) { + if (compatibleRunningAvds.size() >= 2) { message = "Automatic Target Mode: Several compatible targets. Please select a target device."; } else if (hasDevice) { message = "Automatic Target Mode: Unable to detect device compatibility. Please select a target device."; @@ -849,7 +849,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener synchronized (sListLock) { mWaitingForEmulatorLaunches.add(launchInfo); AdtPlugin.printToConsole(project, "Launching a new emulator."); - boolean status = launchEmulator(config, response.vmToLaunch); + boolean status = launchEmulator(config, response.avdToLaunch); if (status == false) { // launching the emulator failed! @@ -1323,7 +1323,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } } - private boolean launchEmulator(AndroidLaunchConfiguration config, VmInfo vmToLaunch) { + private boolean launchEmulator(AndroidLaunchConfiguration config, AvdInfo avdToLaunch) { // split the custom command line in segments ArrayList<String> customArgs = new ArrayList<String>(); @@ -1353,8 +1353,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener ArrayList<String> list = new ArrayList<String>(); list.add(AdtPlugin.getOsAbsoluteEmulator()); - list.add(FLAG_VM); - list.add(vmToLaunch.getName()); + list.add(FLAG_AVD); + list.add(avdToLaunch.getName()); if (config.mNetworkSpeed != null) { list.add(FLAG_NETSPEED); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java index 19ec9a7..05bc171 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java @@ -31,7 +31,7 @@ import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController.Delay import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.ddms.DdmsPlugin; import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.vm.VmManager.VmInfo; +import com.android.sdklib.avd.AvdManager.AvdInfo; import org.eclipse.core.resources.IProject; import org.eclipse.jface.preference.IPreferenceStore; @@ -70,10 +70,10 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener private final static int ICON_WIDTH = 16; private final static String PREFS_COL_SERIAL = "deviceChooser.serial"; //$NON-NLS-1$ - private final static String PREFS_COL_STATE = "deviceChooser.state"; //$NON-NLS-1$ - private final static String PREFS_COL_VM = "deviceChooser.vm"; //$NON-NLS-1$ + private final static String PREFS_COL_STATE = "deviceChooser.state"; //$NON-NLS-1$ + private final static String PREFS_COL_AVD = "deviceChooser.avd"; //$NON-NLS-1$ private final static String PREFS_COL_TARGET = "deviceChooser.target"; //$NON-NLS-1$ - private final static String PREFS_COL_DEBUG = "deviceChooser.debug"; //$NON-NLS-1$ + private final static String PREFS_COL_DEBUG = "deviceChooser.debug"; //$NON-NLS-1$ private Table mDeviceTable; private TableViewer mViewer; @@ -149,8 +149,8 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener return mNoMatchImage; } } else { - // get the VmInfo - VmInfo info = mSdk.getVmManager().getVm(device.getVmName()); + // get the AvdInfo + AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName()); if (info == null) { return mWarningImage; } @@ -171,13 +171,13 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener return device.getSerialNumber(); case 1: if (device.isEmulator()) { - return device.getVmName(); + return device.getAvdName(); } else { - return "N/A"; // devices don't have VM names. + return "N/A"; // devices don't have AVD names. } case 2: if (device.isEmulator()) { - VmInfo info = mSdk.getVmManager().getVm(device.getVmName()); + AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName()); if (info == null) { return "?"; } @@ -221,7 +221,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener public static class DeviceChooserResponse { public boolean mustContinue; public boolean mustLaunchEmulator; - public VmInfo vmToLaunch; + public AvdInfo avdToLaunch; public Device deviceToUse; } @@ -314,9 +314,9 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener SWT.LEFT, "AAA+AAAAAAAAAAAAAAAAAAA", //$NON-NLS-1$ PREFS_COL_SERIAL, store); - TableHelper.createTableColumn(mDeviceTable, "VM Name", + TableHelper.createTableColumn(mDeviceTable, "AVD Name", SWT.LEFT, "engineering", //$NON-NLS-1$ - PREFS_COL_VM, store); + PREFS_COL_AVD, store); TableHelper.createTableColumn(mDeviceTable, "Target", SWT.LEFT, "AAA+Android 9.9.9", //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java index 68deec3..bbd320b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java @@ -80,7 +80,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { */ public static final String ATTR_ACTIVITY = AdtPlugin.PLUGIN_ID + ".activity"; //$NON-NLS-1$ - public static final String ATTR_VM_NAME = AdtPlugin.PLUGIN_ID + ".vm"; //$NON-NLS-1$ + public static final String ATTR_AVD_NAME = AdtPlugin.PLUGIN_ID + ".avd"; //$NON-NLS-1$ public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java index f4f5281..a581e5c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java @@ -22,9 +22,9 @@ import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.ddms.DdmsPlugin; import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.vm.VmManager; -import com.android.sdklib.vm.VmManager.VmInfo; -import com.android.sdkuilib.VmSelector; +import com.android.sdklib.avd.AvdManager; +import com.android.sdklib.avd.AvdManager.AvdInfo; +import com.android.sdkuilib.AvdSelector; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; @@ -75,7 +75,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { private Button mAutoTargetButton; private Button mManualTargetButton; - private VmSelector mPreferredVmSelector; + private AvdSelector mPreferredAvdSelector; private Combo mSpeedCombo; @@ -163,11 +163,11 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { } }); - new Label(targetModeGroup, SWT.NONE).setText("Preferred VM"); - VmInfo[] vms = new VmInfo[0]; - mPreferredVmSelector = new VmSelector(targetModeGroup, vms, + new Label(targetModeGroup, SWT.NONE).setText("Preferred Android Virtual Device"); + AvdInfo[] avds = new AvdInfo[0]; + mPreferredAvdSelector = new AvdSelector(targetModeGroup, avds, false /*allowMultipleSelection*/); - mPreferredVmSelector.setSelectionListener(new SelectionAdapter() { + mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { updateLaunchConfigurationDialog(); @@ -277,7 +277,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { * @see org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration) */ public void initializeFrom(ILaunchConfiguration configuration) { - VmManager vmManager = Sdk.getCurrent().getVmManager(); + AvdManager avdManager = Sdk.getCurrent().getAvdManager(); boolean value = LaunchConfigDelegate.DEFAULT_TARGET_MODE; // true == automatic try { @@ -311,34 +311,34 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { } } - // update the VM list - VmInfo[] vms = null; - if (vmManager != null) { - vms = vmManager.getVms(); + // update the AVD list + AvdInfo[] avds = null; + if (avdManager != null) { + avds = avdManager.getAvds(); } IAndroidTarget projectTarget = null; if (project != null) { projectTarget = Sdk.getCurrent().getTarget(project); } else { - vms = null; // no project? we don't want to display any "compatible" VMs. + avds = null; // no project? we don't want to display any "compatible" AVDs. } - mPreferredVmSelector.setVms(vms, projectTarget); + mPreferredAvdSelector.setAvds(avds, projectTarget); stringValue = ""; try { - stringValue = configuration.getAttribute(LaunchConfigDelegate.ATTR_VM_NAME, + stringValue = configuration.getAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, stringValue); } catch (CoreException e) { // let's not do anything here, we'll use the default value } - if (stringValue != null && stringValue.length() > 0 && vmManager != null) { - VmInfo targetVm = vmManager.getVm(stringValue); - mPreferredVmSelector.setSelection(targetVm); + if (stringValue != null && stringValue.length() > 0 && avdManager != null) { + AvdInfo targetAvd = avdManager.getAvd(stringValue); + mPreferredAvdSelector.setSelection(targetAvd); } else { - mPreferredVmSelector.setSelection(null); + mPreferredAvdSelector.setSelection(null); } value = LaunchConfigDelegate.DEFAULT_WIPE_DATA; @@ -404,11 +404,11 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { public void performApply(ILaunchConfigurationWorkingCopy configuration) { configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, mAutoTargetButton.getSelection()); - VmInfo vm = mPreferredVmSelector.getFirstSelected(); - if (vm != null) { - configuration.setAttribute(LaunchConfigDelegate.ATTR_VM_NAME, vm.getName()); + AvdInfo avd = mPreferredAvdSelector.getFirstSelected(); + if (avd != null) { + configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, avd.getName()); } else { - configuration.setAttribute(LaunchConfigDelegate.ATTR_VM_NAME, (String)null); + configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String)null); } configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED, mSpeedCombo.getSelectionIndex()); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java index 8678923..cbeddd7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java @@ -361,7 +361,7 @@ public final class ProjectHelper { } /** - * Returns a {@link IProject} by its running application name, as it returned by the VM. + * Returns a {@link IProject} by its running application name, as it returned by the AVD. * <p/> * <var>applicationName</var> will in most case be the package declared in the manifest, but * can, in some cases, be a custom process name declared in the manifest, in the diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java index 172b4ae..01b722f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java @@ -23,9 +23,9 @@ import com.android.sdklib.IAndroidTarget; import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkManager; +import com.android.sdklib.avd.AvdManager; import com.android.sdklib.project.ProjectProperties; import com.android.sdklib.project.ProjectProperties.PropertyType; -import com.android.sdklib.vm.VmManager; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IStatus; @@ -52,7 +52,7 @@ public class Sdk { private static Sdk sCurrentSdk = null; private final SdkManager mManager; - private final VmManager mVmManager; + private final AvdManager mAvdManager; private final HashMap<IProject, IAndroidTarget> mProjectMap = new HashMap<IProject, IAndroidTarget>(); @@ -95,13 +95,13 @@ public class Sdk { // get an SdkManager object for the location SdkManager manager = SdkManager.createManager(sdkLocation, log); if (manager != null) { - VmManager vmManager = null; + AvdManager avdManager = null; try { - vmManager = new VmManager(manager, log); + avdManager = new AvdManager(manager, log); } catch (AndroidLocationException e) { - log.error(e, "Error parsing the VMs"); + log.error(e, "Error parsing the AVDs"); } - sCurrentSdk = new Sdk(manager, vmManager); + sCurrentSdk = new Sdk(manager, avdManager); return sCurrentSdk; } else { StringBuilder sb = new StringBuilder("Error Loading the SDK:\n"); @@ -255,16 +255,16 @@ public class Sdk { } /** - * Returns the {@link VmManager}. If the VmManager failed to parse the VM folder, this could + * Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could * be <code>null</code>. */ - public VmManager getVmManager() { - return mVmManager; + public AvdManager getAvdManager() { + return mAvdManager; } - private Sdk(SdkManager manager, VmManager vmManager) { + private Sdk(SdkManager manager, AvdManager avdManager) { mManager = manager; - mVmManager = vmManager; + mAvdManager = avdManager; // pre-compute some paths mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java index 0f388f4..d5ee2ca 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java @@ -187,10 +187,10 @@ public final class CustomViewDescriptorService { /** * Computes (if needed) and returns the {@link ElementDescriptor} for the specified type. * - * @param type + * @param type * @param project * @param typeHierarchy - * @return A ViewElementDescriptor + * @return A ViewElementDescriptor or null if type or typeHierarchy is null. */ private ViewElementDescriptor getDescriptor(IType type, IProject project, ITypeHierarchy typeHierarchy) { @@ -198,12 +198,17 @@ public final class CustomViewDescriptorService { List<ElementDescriptor> builtInList = null; Sdk currentSdk = Sdk.getCurrent(); - IAndroidTarget target = currentSdk.getTarget(project); + IAndroidTarget target = currentSdk == null ? null : currentSdk.getTarget(project); if (target != null) { AndroidTargetData data = currentSdk.getTargetData(target); builtInList = data.getLayoutDescriptors().getViewDescriptors(); } + // give up if there's no type + if (type == null) { + return null; + } + String canonicalName = type.getFullyQualifiedName(); if (builtInList != null) { @@ -218,6 +223,11 @@ public final class CustomViewDescriptorService { } // it's not a built-in class? Lets look if the superclass is built-in + // give up if there's no type + if (typeHierarchy == null) { + return null; + } + IType parentType = typeHierarchy.getSuperclass(type); if (parentType != null) { ViewElementDescriptor parentDescriptor = getDescriptor(parentType, project, diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java index 5908574..4a9fbb1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java @@ -79,7 +79,7 @@ public abstract class UiAbstractTextAttributeNode extends UiAttributeNode public void updateValue(Node xml_attribute_node) { mCurrentValue = DEFAULT_VALUE; if (xml_attribute_node != null) { - mCurrentValue = xml_attribute_node.getNodeValue().trim(); + mCurrentValue = xml_attribute_node.getNodeValue(); } if (isValid() && !getTextWidgetValue().equals(mCurrentValue)) { @@ -101,7 +101,7 @@ public abstract class UiAbstractTextAttributeNode extends UiAttributeNode public void commit() { UiElementNode parent = getUiParent(); if (parent != null && isValid() && isDirty()) { - String value = getTextWidgetValue().trim(); + String value = getTextWidgetValue(); if (parent.commitAttributeToXml(this, value)) { mCurrentValue = value; setDirty(false); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/project/ProjectHelperTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/project/ProjectHelperTest.java index 5a89d01..8c52d81 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/project/ProjectHelperTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/project/ProjectHelperTest.java @@ -22,7 +22,6 @@ import com.android.ide.eclipse.mock.JavaProjectMock; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.launching.JavaRuntime; import junit.framework.TestCase; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java index 872938b..8af7e02 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java @@ -28,7 +28,7 @@ import java.util.HashMap; import junit.framework.TestCase; /** - * Unit Test for {@link FrameworkClassLoader}. + * Unit Test for {@link AndroidJarLoader}. * * Uses the classes jar.example.Class1/Class2 stored in tests/data/jar_example.jar. */ @@ -36,7 +36,7 @@ public class AndroidJarLoaderTest extends TestCase { private AndroidJarLoader mFrameworkClassLoader; - /** Creates an instance of {@link FrameworkClassLoader} on our test data JAR */ + /** Creates an instance of {@link AndroidJarLoader} on our test data JAR */ @Override public void setUp() throws Exception { String jarfilePath = AdtTestData.getInstance().getTestFilePath("jar_example.jar"); //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/LayoutParamsParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/LayoutParamsParserTest.java index b66fcd6..cedf4d4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/LayoutParamsParserTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/LayoutParamsParserTest.java @@ -35,7 +35,7 @@ import junit.framework.TestCase; * Test the inner private methods of PlatformDataParser. * * Convention: method names that start with an underscore are actually local wrappers - * that call private methods from {@link FrameworkResourceParser} using reflection. + * that call private methods from {@link AndroidTargetParser} using reflection. * This is inspired by the Python coding rule which mandates underscores prefixes for * "private" methods. */ @@ -131,6 +131,7 @@ public class LayoutParamsParserTest extends TestCase { //---- access to private methods /** Calls the private constructor of the parser */ + @SuppressWarnings("unused") private AndroidTargetParser _Constructor(String osJarPath) throws Exception { Constructor<AndroidTargetParser> constructor = AndroidTargetParser.class.getDeclaredConstructor(String.class); @@ -139,6 +140,7 @@ public class LayoutParamsParserTest extends TestCase { } /** calls the private getLayoutClasses() of the parser */ + @SuppressWarnings("unused") private void _getLayoutClasses() throws Exception { Method method = AndroidTargetParser.class.getDeclaredMethod("getLayoutClasses"); //$NON-NLS-1$ method.setAccessible(true); @@ -146,6 +148,7 @@ public class LayoutParamsParserTest extends TestCase { } /** calls the private addGroup() of the parser */ + @SuppressWarnings("unused") private ViewClassInfo _addGroup(Class<?> groupClass) throws Exception { Method method = LayoutParamsParser.class.getDeclaredMethod("addGroup", //$NON-NLS-1$ IClassDescriptor.class); @@ -154,6 +157,7 @@ public class LayoutParamsParserTest extends TestCase { } /** calls the private addLayoutParams() of the parser */ + @SuppressWarnings("unused") private LayoutParamsInfo _addLayoutParams(Class<?> groupClass) throws Exception { Method method = LayoutParamsParser.class.getDeclaredMethod("addLayoutParams", //$NON-NLS-1$ IClassDescriptor.class); diff --git a/scripts/app_engine_server/LICENSE b/scripts/app_engine_server/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/scripts/app_engine_server/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/scripts/app_engine_server/app.yaml b/scripts/app_engine_server/app.yaml new file mode 100755 index 0000000..1fb50c7 --- /dev/null +++ b/scripts/app_engine_server/app.yaml @@ -0,0 +1,16 @@ +application: androidappdocs-staging +version: 1 +runtime: python +api_version: 1 + +handlers: +- url: /gae_shell/static + static_dir: gae_shell/static + expiration: 1d + +- url: /gae_shell/.* + script: /gae_shell/shell.py + login: admin + +- url: .* + script: main.py diff --git a/scripts/app_engine_server/gae_shell/README b/scripts/app_engine_server/gae_shell/README new file mode 100644 index 0000000..5b0089f --- /dev/null +++ b/scripts/app_engine_server/gae_shell/README @@ -0,0 +1,17 @@ +An interactive, stateful AJAX shell that runs Python code on the server. + +Part of http://code.google.com/p/google-app-engine-samples/. + +May be run as a standalone app or in an existing app as an admin-only handler. +Can be used for system administration tasks, as an interactive way to try out +APIs, or as a debugging aid during development. + +The logging, os, sys, db, and users modules are imported automatically. + +Interpreter state is stored in the datastore so that variables, function +definitions, and other values in the global and local namespaces can be used +across commands. + +To use the shell in your app, copy shell.py, static/*, and templates/* into +your app's source directory. Then, copy the URL handlers from app.yaml into +your app.yaml. diff --git a/scripts/app_engine_server/gae_shell/__init__.py b/scripts/app_engine_server/gae_shell/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/scripts/app_engine_server/gae_shell/__init__.py diff --git a/scripts/app_engine_server/gae_shell/__init__.pyc b/scripts/app_engine_server/gae_shell/__init__.pyc Binary files differnew file mode 100644 index 0000000..84951e9 --- /dev/null +++ b/scripts/app_engine_server/gae_shell/__init__.pyc diff --git a/scripts/app_engine_server/gae_shell/shell.py b/scripts/app_engine_server/gae_shell/shell.py new file mode 100755 index 0000000..df2fb17 --- /dev/null +++ b/scripts/app_engine_server/gae_shell/shell.py @@ -0,0 +1,308 @@ +#!/usr/bin/python +# +# Copyright 2007 Google Inc. +# +# 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. + +""" +An interactive, stateful AJAX shell that runs Python code on the server. + +Part of http://code.google.com/p/google-app-engine-samples/. + +May be run as a standalone app or in an existing app as an admin-only handler. +Can be used for system administration tasks, as an interactive way to try out +APIs, or as a debugging aid during development. + +The logging, os, sys, db, and users modules are imported automatically. + +Interpreter state is stored in the datastore so that variables, function +definitions, and other values in the global and local namespaces can be used +across commands. + +To use the shell in your app, copy shell.py, static/*, and templates/* into +your app's source directory. Then, copy the URL handlers from app.yaml into +your app.yaml. + +TODO: unit tests! +""" + +import logging +import new +import os +import pickle +import sys +import traceback +import types +import wsgiref.handlers + +from google.appengine.api import users +from google.appengine.ext import db +from google.appengine.ext import webapp +from google.appengine.ext.webapp import template + + +# Set to True if stack traces should be shown in the browser, etc. +_DEBUG = True + +# The entity kind for shell sessions. Feel free to rename to suit your app. +_SESSION_KIND = '_Shell_Session' + +# Types that can't be pickled. +UNPICKLABLE_TYPES = ( + types.ModuleType, + types.TypeType, + types.ClassType, + types.FunctionType, + ) + +# Unpicklable statements to seed new sessions with. +INITIAL_UNPICKLABLES = [ + 'import logging', + 'import os', + 'import sys', + 'from google.appengine.ext import db', + 'from google.appengine.api import users', + ] + + +class Session(db.Model): + """A shell session. Stores the session's globals. + + Each session globals is stored in one of two places: + + If the global is picklable, it's stored in the parallel globals and + global_names list properties. (They're parallel lists to work around the + unfortunate fact that the datastore can't store dictionaries natively.) + + If the global is not picklable (e.g. modules, classes, and functions), or if + it was created by the same statement that created an unpicklable global, + it's not stored directly. Instead, the statement is stored in the + unpicklables list property. On each request, before executing the current + statement, the unpicklable statements are evaluated to recreate the + unpicklable globals. + + The unpicklable_names property stores all of the names of globals that were + added by unpicklable statements. When we pickle and store the globals after + executing a statement, we skip the ones in unpicklable_names. + + Using Text instead of string is an optimization. We don't query on any of + these properties, so they don't need to be indexed. + """ + global_names = db.ListProperty(db.Text) + globals = db.ListProperty(db.Blob) + unpicklable_names = db.ListProperty(db.Text) + unpicklables = db.ListProperty(db.Text) + + def set_global(self, name, value): + """Adds a global, or updates it if it already exists. + + Also removes the global from the list of unpicklable names. + + Args: + name: the name of the global to remove + value: any picklable value + """ + blob = db.Blob(pickle.dumps(value)) + + if name in self.global_names: + index = self.global_names.index(name) + self.globals[index] = blob + else: + self.global_names.append(db.Text(name)) + self.globals.append(blob) + + self.remove_unpicklable_name(name) + + def remove_global(self, name): + """Removes a global, if it exists. + + Args: + name: string, the name of the global to remove + """ + if name in self.global_names: + index = self.global_names.index(name) + del self.global_names[index] + del self.globals[index] + + def globals_dict(self): + """Returns a dictionary view of the globals. + """ + return dict((name, pickle.loads(val)) + for name, val in zip(self.global_names, self.globals)) + + def add_unpicklable(self, statement, names): + """Adds a statement and list of names to the unpicklables. + + Also removes the names from the globals. + + Args: + statement: string, the statement that created new unpicklable global(s). + names: list of strings; the names of the globals created by the statement. + """ + self.unpicklables.append(db.Text(statement)) + + for name in names: + self.remove_global(name) + if name not in self.unpicklable_names: + self.unpicklable_names.append(db.Text(name)) + + def remove_unpicklable_name(self, name): + """Removes a name from the list of unpicklable names, if it exists. + + Args: + name: string, the name of the unpicklable global to remove + """ + if name in self.unpicklable_names: + self.unpicklable_names.remove(name) + + +class FrontPageHandler(webapp.RequestHandler): + """Creates a new session and renders the shell.html template. + """ + + def get(self): + # set up the session. TODO: garbage collect old shell sessions + session_key = self.request.get('session') + if session_key: + session = Session.get(session_key) + else: + # create a new session + session = Session() + session.unpicklables = [db.Text(line) for line in INITIAL_UNPICKLABLES] + session_key = session.put() + + template_file = os.path.join(os.path.dirname(__file__), 'templates', + 'shell.html') + session_url = '/?session=%s' % session_key + vars = { 'server_software': os.environ['SERVER_SOFTWARE'], + 'python_version': sys.version, + 'session': str(session_key), + 'user': users.get_current_user(), + 'login_url': users.create_login_url(session_url), + 'logout_url': users.create_logout_url(session_url), + } + rendered = webapp.template.render(template_file, vars, debug=_DEBUG) + self.response.out.write(rendered) + + +class StatementHandler(webapp.RequestHandler): + """Evaluates a python statement in a given session and returns the result. + """ + + def get(self): + self.response.headers['Content-Type'] = 'text/plain' + + # extract the statement to be run + statement = self.request.get('statement') + if not statement: + return + + # the python compiler doesn't like network line endings + statement = statement.replace('\r\n', '\n') + + # add a couple newlines at the end of the statement. this makes + # single-line expressions such as 'class Foo: pass' evaluate happily. + statement += '\n\n' + + # log and compile the statement up front + try: + logging.info('Compiling and evaluating:\n%s' % statement) + compiled = compile(statement, '<string>', 'single') + except: + self.response.out.write(traceback.format_exc()) + return + + # create a dedicated module to be used as this statement's __main__ + statement_module = new.module('__main__') + + # use this request's __builtin__, since it changes on each request. + # this is needed for import statements, among other things. + import __builtin__ + statement_module.__builtins__ = __builtin__ + + # load the session from the datastore + session = Session.get(self.request.get('session')) + + # swap in our custom module for __main__. then unpickle the session + # globals, run the statement, and re-pickle the session globals, all + # inside it. + old_main = sys.modules.get('__main__') + try: + sys.modules['__main__'] = statement_module + statement_module.__name__ = '__main__' + + # re-evaluate the unpicklables + for code in session.unpicklables: + exec code in statement_module.__dict__ + + # re-initialize the globals + for name, val in session.globals_dict().items(): + try: + statement_module.__dict__[name] = val + except: + msg = 'Dropping %s since it could not be unpickled.\n' % name + self.response.out.write(msg) + logging.warning(msg + traceback.format_exc()) + session.remove_global(name) + + # run! + old_globals = dict(statement_module.__dict__) + try: + old_stdout = sys.stdout + old_stderr = sys.stderr + try: + sys.stdout = self.response.out + sys.stderr = self.response.out + exec compiled in statement_module.__dict__ + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + except: + self.response.out.write(traceback.format_exc()) + return + + # extract the new globals that this statement added + new_globals = {} + for name, val in statement_module.__dict__.items(): + if name not in old_globals or val != old_globals[name]: + new_globals[name] = val + + if True in [isinstance(val, UNPICKLABLE_TYPES) + for val in new_globals.values()]: + # this statement added an unpicklable global. store the statement and + # the names of all of the globals it added in the unpicklables. + session.add_unpicklable(statement, new_globals.keys()) + logging.debug('Storing this statement as an unpicklable.') + + else: + # this statement didn't add any unpicklables. pickle and store the + # new globals back into the datastore. + for name, val in new_globals.items(): + if not name.startswith('__'): + session.set_global(name, val) + + finally: + sys.modules['__main__'] = old_main + + session.put() + + +def main(): + application = webapp.WSGIApplication( + [('/gae_shell/', FrontPageHandler), + ('/gae_shell/shell.do', StatementHandler)], debug=_DEBUG) + wsgiref.handlers.CGIHandler().run(application) + + +if __name__ == '__main__': + main() diff --git a/scripts/app_engine_server/gae_shell/shell.py~ b/scripts/app_engine_server/gae_shell/shell.py~ new file mode 100755 index 0000000..dee9fdb --- /dev/null +++ b/scripts/app_engine_server/gae_shell/shell.py~ @@ -0,0 +1,308 @@ +#!/usr/bin/python +# +# Copyright 2007 Google Inc. +# +# 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. + +""" +An interactive, stateful AJAX shell that runs Python code on the server. + +Part of http://code.google.com/p/google-app-engine-samples/. + +May be run as a standalone app or in an existing app as an admin-only handler. +Can be used for system administration tasks, as an interactive way to try out +APIs, or as a debugging aid during development. + +The logging, os, sys, db, and users modules are imported automatically. + +Interpreter state is stored in the datastore so that variables, function +definitions, and other values in the global and local namespaces can be used +across commands. + +To use the shell in your app, copy shell.py, static/*, and templates/* into +your app's source directory. Then, copy the URL handlers from app.yaml into +your app.yaml. + +TODO: unit tests! +""" + +import logging +import new +import os +import pickle +import sys +import traceback +import types +import wsgiref.handlers + +from google.appengine.api import users +from google.appengine.ext import db +from google.appengine.ext import webapp +from google.appengine.ext.webapp import template + + +# Set to True if stack traces should be shown in the browser, etc. +_DEBUG = True + +# The entity kind for shell sessions. Feel free to rename to suit your app. +_SESSION_KIND = '_Shell_Session' + +# Types that can't be pickled. +UNPICKLABLE_TYPES = ( + types.ModuleType, + types.TypeType, + types.ClassType, + types.FunctionType, + ) + +# Unpicklable statements to seed new sessions with. +INITIAL_UNPICKLABLES = [ + 'import logging', + 'import os', + 'import sys', + 'from google.appengine.ext import db', + 'from google.appengine.api import users', + ] + + +class Session(db.Model): + """A shell session. Stores the session's globals. + + Each session globals is stored in one of two places: + + If the global is picklable, it's stored in the parallel globals and + global_names list properties. (They're parallel lists to work around the + unfortunate fact that the datastore can't store dictionaries natively.) + + If the global is not picklable (e.g. modules, classes, and functions), or if + it was created by the same statement that created an unpicklable global, + it's not stored directly. Instead, the statement is stored in the + unpicklables list property. On each request, before executing the current + statement, the unpicklable statements are evaluated to recreate the + unpicklable globals. + + The unpicklable_names property stores all of the names of globals that were + added by unpicklable statements. When we pickle and store the globals after + executing a statement, we skip the ones in unpicklable_names. + + Using Text instead of string is an optimization. We don't query on any of + these properties, so they don't need to be indexed. + """ + global_names = db.ListProperty(db.Text) + globals = db.ListProperty(db.Blob) + unpicklable_names = db.ListProperty(db.Text) + unpicklables = db.ListProperty(db.Text) + + def set_global(self, name, value): + """Adds a global, or updates it if it already exists. + + Also removes the global from the list of unpicklable names. + + Args: + name: the name of the global to remove + value: any picklable value + """ + blob = db.Blob(pickle.dumps(value)) + + if name in self.global_names: + index = self.global_names.index(name) + self.globals[index] = blob + else: + self.global_names.append(db.Text(name)) + self.globals.append(blob) + + self.remove_unpicklable_name(name) + + def remove_global(self, name): + """Removes a global, if it exists. + + Args: + name: string, the name of the global to remove + """ + if name in self.global_names: + index = self.global_names.index(name) + del self.global_names[index] + del self.globals[index] + + def globals_dict(self): + """Returns a dictionary view of the globals. + """ + return dict((name, pickle.loads(val)) + for name, val in zip(self.global_names, self.globals)) + + def add_unpicklable(self, statement, names): + """Adds a statement and list of names to the unpicklables. + + Also removes the names from the globals. + + Args: + statement: string, the statement that created new unpicklable global(s). + names: list of strings; the names of the globals created by the statement. + """ + self.unpicklables.append(db.Text(statement)) + + for name in names: + self.remove_global(name) + if name not in self.unpicklable_names: + self.unpicklable_names.append(db.Text(name)) + + def remove_unpicklable_name(self, name): + """Removes a name from the list of unpicklable names, if it exists. + + Args: + name: string, the name of the unpicklable global to remove + """ + if name in self.unpicklable_names: + self.unpicklable_names.remove(name) + + +class FrontPageHandler(webapp.RequestHandler): + """Creates a new session and renders the shell.html template. + """ + + def get(self): + # set up the session. TODO: garbage collect old shell sessions + session_key = self.request.get('session') + if session_key: + session = Session.get(session_key) + else: + # create a new session + session = Session() + session.unpicklables = [db.Text(line) for line in INITIAL_UNPICKLABLES] + session_key = session.put() + + template_file = os.path.join(os.path.dirname(__file__), 'templates', + 'shell.html') + session_url = '/?session=%s' % session_key + vars = { 'server_software': os.environ['SERVER_SOFTWARE'], + 'python_version': sys.version, + 'session': str(session_key), + 'user': users.get_current_user(), + 'login_url': users.create_login_url(session_url), + 'logout_url': users.create_logout_url(session_url), + } + rendered = webapp.template.render(template_file, vars, debug=_DEBUG) + self.response.out.write(rendered) + + +class StatementHandler(webapp.RequestHandler): + """Evaluates a python statement in a given session and returns the result. + """ + + def get(self): + self.response.headers['Content-Type'] = 'text/plain' + + # extract the statement to be run + statement = self.request.get('statement') + if not statement: + return + + # the python compiler doesn't like network line endings + statement = statement.replace('\r\n', '\n') + + # add a couple newlines at the end of the statement. this makes + # single-line expressions such as 'class Foo: pass' evaluate happily. + statement += '\n\n' + + # log and compile the statement up front + try: + logging.info('Compiling and evaluating:\n%s' % statement) + compiled = compile(statement, '<string>', 'single') + except: + self.response.out.write(traceback.format_exc()) + return + + # create a dedicated module to be used as this statement's __main__ + statement_module = new.module('__main__') + + # use this request's __builtin__, since it changes on each request. + # this is needed for import statements, among other things. + import __builtin__ + statement_module.__builtins__ = __builtin__ + + # load the session from the datastore + session = Session.get(self.request.get('session')) + + # swap in our custom module for __main__. then unpickle the session + # globals, run the statement, and re-pickle the session globals, all + # inside it. + old_main = sys.modules.get('__main__') + try: + sys.modules['__main__'] = statement_module + statement_module.__name__ = '__main__' + + # re-evaluate the unpicklables + for code in session.unpicklables: + exec code in statement_module.__dict__ + + # re-initialize the globals + for name, val in session.globals_dict().items(): + try: + statement_module.__dict__[name] = val + except: + msg = 'Dropping %s since it could not be unpickled.\n' % name + self.response.out.write(msg) + logging.warning(msg + traceback.format_exc()) + session.remove_global(name) + + # run! + old_globals = dict(statement_module.__dict__) + try: + old_stdout = sys.stdout + old_stderr = sys.stderr + try: + sys.stdout = self.response.out + sys.stderr = self.response.out + exec compiled in statement_module.__dict__ + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + except: + self.response.out.write(traceback.format_exc()) + return + + # extract the new globals that this statement added + new_globals = {} + for name, val in statement_module.__dict__.items(): + if name not in old_globals or val != old_globals[name]: + new_globals[name] = val + + if True in [isinstance(val, UNPICKLABLE_TYPES) + for val in new_globals.values()]: + # this statement added an unpicklable global. store the statement and + # the names of all of the globals it added in the unpicklables. + session.add_unpicklable(statement, new_globals.keys()) + logging.debug('Storing this statement as an unpicklable.') + + else: + # this statement didn't add any unpicklables. pickle and store the + # new globals back into the datastore. + for name, val in new_globals.items(): + if not name.startswith('__'): + session.set_global(name, val) + + finally: + sys.modules['__main__'] = old_main + + session.put() + + +def main(): + application = webapp.WSGIApplication( + [('/', FrontPageHandler), + ('/shell.do', StatementHandler)], debug=_DEBUG) + wsgiref.handlers.CGIHandler().run(application) + + +if __name__ == '__main__': + main() diff --git a/scripts/app_engine_server/gae_shell/static/shell.js b/scripts/app_engine_server/gae_shell/static/shell.js new file mode 100644 index 0000000..4aa1583 --- /dev/null +++ b/scripts/app_engine_server/gae_shell/static/shell.js @@ -0,0 +1,195 @@ +// Copyright 2007 Google Inc. +// +// 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. + +/** + * @fileoverview + * Javascript code for the interactive AJAX shell. + * + * Part of http://code.google.com/p/google-app-engine-samples/. + * + * Includes a function (shell.runStatement) that sends the current python + * statement in the shell prompt text box to the server, and a callback + * (shell.done) that displays the results when the XmlHttpRequest returns. + * + * Also includes cross-browser code (shell.getXmlHttpRequest) to get an + * XmlHttpRequest. + */ + +/** + * Shell namespace. + * @type {Object} + */ +var shell = {} + +/** + * The shell history. history is an array of strings, ordered oldest to + * newest. historyCursor is the current history element that the user is on. + * + * The last history element is the statement that the user is currently + * typing. When a statement is run, it's frozen in the history, a new history + * element is added to the end of the array for the new statement, and + * historyCursor is updated to point to the new element. + * + * @type {Array} + */ +shell.history = ['']; + +/** + * See {shell.history} + * @type {number} + */ +shell.historyCursor = 0; + +/** + * A constant for the XmlHttpRequest 'done' state. + * @type Number + */ +shell.DONE_STATE = 4; + +/** + * A cross-browser function to get an XmlHttpRequest object. + * + * @return {XmlHttpRequest?} a new XmlHttpRequest + */ +shell.getXmlHttpRequest = function() { + if (window.XMLHttpRequest) { + return new XMLHttpRequest(); + } else if (window.ActiveXObject) { + try { + return new ActiveXObject('Msxml2.XMLHTTP'); + } catch(e) { + return new ActiveXObject('Microsoft.XMLHTTP'); + } + } + + return null; +}; + +/** + * This is the prompt textarea's onkeypress handler. Depending on the key that + * was pressed, it will run the statement, navigate the history, or update the + * current statement in the history. + * + * @param {Event} event the keypress event + * @return {Boolean} false to tell the browser not to submit the form. + */ +shell.onPromptKeyPress = function(event) { + var statement = document.getElementById('statement'); + + if (this.historyCursor == this.history.length - 1) { + // we're on the current statement. update it in the history before doing + // anything. + this.history[this.historyCursor] = statement.value; + } + + // should we pull something from the history? + if (event.ctrlKey && event.keyCode == 38 /* up arrow */) { + if (this.historyCursor > 0) { + statement.value = this.history[--this.historyCursor]; + } + return false; + } else if (event.ctrlKey && event.keyCode == 40 /* down arrow */) { + if (this.historyCursor < this.history.length - 1) { + statement.value = this.history[++this.historyCursor]; + } + return false; + } else if (!event.altKey) { + // probably changing the statement. update it in the history. + this.historyCursor = this.history.length - 1; + this.history[this.historyCursor] = statement.value; + } + + // should we submit? + var ctrlEnter = (document.getElementById('submit_key').value == 'ctrl-enter'); + if (event.keyCode == 13 /* enter */ && !event.altKey && !event.shiftKey && + event.ctrlKey == ctrlEnter) { + return this.runStatement(); + } +}; + +/** + * The XmlHttpRequest callback. If the request succeeds, it adds the command + * and its resulting output to the shell history div. + * + * @param {XmlHttpRequest} req the XmlHttpRequest we used to send the current + * statement to the server + */ +shell.done = function(req) { + if (req.readyState == this.DONE_STATE) { + var statement = document.getElementById('statement') + statement.className = 'prompt'; + + // add the command to the shell output + var output = document.getElementById('output'); + + output.value += '\n>>> ' + statement.value; + statement.value = ''; + + // add a new history element + this.history.push(''); + this.historyCursor = this.history.length - 1; + + // add the command's result + var result = req.responseText.replace(/^\s*|\s*$/g, ''); // trim whitespace + if (result != '') + output.value += '\n' + result; + + // scroll to the bottom + output.scrollTop = output.scrollHeight; + if (output.createTextRange) { + var range = output.createTextRange(); + range.collapse(false); + range.select(); + } + } +}; + +/** + * This is the form's onsubmit handler. It sends the python statement to the + * server, and registers shell.done() as the callback to run when it returns. + * + * @return {Boolean} false to tell the browser not to submit the form. + */ +shell.runStatement = function() { + var form = document.getElementById('form'); + + // build a XmlHttpRequest + var req = this.getXmlHttpRequest(); + if (!req) { + document.getElementById('ajax-status').innerHTML = + "<span class='error'>Your browser doesn't support AJAX. :(</span>"; + return false; + } + + req.onreadystatechange = function() { shell.done(req); }; + + // build the query parameter string + var params = ''; + for (i = 0; i < form.elements.length; i++) { + var elem = form.elements[i]; + if (elem.type != 'submit' && elem.type != 'button' && elem.id != 'caret') { + var value = escape(elem.value).replace(/\+/g, '%2B'); // escape ignores + + params += '&' + elem.name + '=' + value; + } + } + + // send the request and tell the user. + document.getElementById('statement').className = 'prompt processing'; + req.open(form.method, form.action + '?' + params, true); + req.setRequestHeader('Content-type', + 'application/x-www-form-urlencoded;charset=UTF-8'); + req.send(null); + + return false; +}; diff --git a/scripts/app_engine_server/gae_shell/static/spinner.gif b/scripts/app_engine_server/gae_shell/static/spinner.gif Binary files differnew file mode 100644 index 0000000..3e58d6e --- /dev/null +++ b/scripts/app_engine_server/gae_shell/static/spinner.gif diff --git a/scripts/app_engine_server/gae_shell/templates/shell.html b/scripts/app_engine_server/gae_shell/templates/shell.html new file mode 100644 index 0000000..123b200 --- /dev/null +++ b/scripts/app_engine_server/gae_shell/templates/shell.html @@ -0,0 +1,122 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<head> +<meta http-equiv="content-type" content="text/html; charset=utf-8" /> +<title> Interactive Shell </title> +<script type="text/javascript" src="/gae_shell/static/shell.js"></script> +<style type="text/css"> +body { + font-family: monospace; + font-size: 10pt; +} + +p { + margin: 0.5em; +} + +.prompt, #output { + width: 45em; + border: 1px solid silver; + background-color: #f5f5f5; + font-size: 10pt; + margin: 0.5em; + padding: 0.5em; + padding-right: 0em; + overflow-x: hidden; +} + +#toolbar { + margin-left: 0.5em; + padding-left: 0.5em; +} + +#caret { + width: 2.5em; + margin-right: 0px; + padding-right: 0px; + border-right: 0px; +} + +#statement { + width: 43em; + margin-left: -1em; + padding-left: 0px; + border-left: 0px; + background-position: top right; + background-repeat: no-repeat; +} + +.processing { + background-image: url("/gae_shell/static/spinner.gif"); +} + +#ajax-status { + font-weight: bold; +} + +.message { + color: #8AD; + font-weight: bold; + font-style: italic; +} + +.error { + color: #F44; +} + +.username { + font-weight: bold; +} + +</style> +</head> + +<body> + +<p> Interactive server-side Python shell for +<a href="http://code.google.com/appengine/">Google App Engine</a>. +(<a href="http://code.google.com/p/google-app-engine-samples/">source</a>) +</p> + +<textarea id="output" rows="22" readonly="readonly"> +{{ server_software }} +Python {{ python_version }} +</textarea> + +<form id="form" action="shell.do" method="get"> + <nobr> + <textarea class="prompt" id="caret" readonly="readonly" rows="4" + onfocus="document.getElementById('statement').focus()" + >>>></textarea> + <textarea class="prompt" name="statement" id="statement" rows="4" + onkeypress="return shell.onPromptKeyPress(event);"></textarea> + </nobr> + <input type="hidden" name="session" value="{{ session }}" /> + <input type="submit" style="display: none" /> +</form> + +<p id="ajax-status"></p> + +<p id="toolbar"> +{% if user %} + <span class="username">{{ user.nickname }}</span> + (<a href="{{ logout_url }}">log out</a>) +{% else %} + <a href="{{ login_url }}">log in</a> +{% endif %} + | Ctrl-Up/Down for history | +<select id="submit_key"> + <option value="enter">Enter</option> + <option value="ctrl-enter" selected="selected">Ctrl-Enter</option> +</select> +<label for="submit_key">submits</label> +</p> + +<script type="text/javascript"> +document.getElementById('statement').focus(); +</script> + +</body> +</html> + diff --git a/scripts/app_engine_server/index.yaml b/scripts/app_engine_server/index.yaml new file mode 100644 index 0000000..8e6046d --- /dev/null +++ b/scripts/app_engine_server/index.yaml @@ -0,0 +1,12 @@ +indexes: + +# AUTOGENERATED + +# This index.yaml is automatically updated whenever the dev_appserver +# detects that a new type of query is run. If you want to manage the +# index.yaml file manually, remove the above marker line (the line +# saying "# AUTOGENERATED"). If you want to manage some indexes +# manually, move them above the marker line. The index.yaml file is +# automatically uploaded to the admin console when you next deploy +# your application using appcfg.py. + diff --git a/scripts/app_engine_server/memcache_zipserve.py b/scripts/app_engine_server/memcache_zipserve.py new file mode 100644 index 0000000..e11cfc5 --- /dev/null +++ b/scripts/app_engine_server/memcache_zipserve.py @@ -0,0 +1,412 @@ +#!/usr/bin/env python +# +# Copyright 2009 Google Inc. +# +# 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. +# + +"""A class to serve pages from zip files and use memcache for performance. + +This contains a class and a function to create an anonymous instance of the +class to serve HTTP GET requests. Memcache is used to increase response speed +and lower processing cycles used in serving. Credit to Guido van Rossum and +his implementation of zipserve which served as a reference as I wrote this. + + MemcachedZipHandler: Class that serves request + create_handler: method to create instance of MemcachedZipHandler +""" + +__author__ = 'jmatt@google.com (Justin Mattson)' + +import email.Utils +import logging +import mimetypes +import time +import zipfile + +from google.appengine.api import memcache +from google.appengine.ext import webapp +from google.appengine.ext.webapp import util + + +def create_handler(zip_files, max_age=None, public=None): + """Factory method to create a MemcachedZipHandler instance. + + Args: + zip_files: A list of file names, or a list of lists of file name, first + member of file mappings. See MemcachedZipHandler documentation for + more information about using the list of lists format + max_age: The maximum client-side cache lifetime + public: Whether this should be declared public in the client-side cache + Returns: + A MemcachedZipHandler wrapped in a pretty, anonymous bow for use with App + Engine + + Raises: + ValueError: if the zip_files argument is not a list + """ + # verify argument integrity. If the argument is passed in list format, + # convert it to list of lists format + + if zip_files and type(zip_files).__name__ == 'list': + num_items = len(zip_files) + while num_items > 0: + if type(zip_files[num_items - 1]).__name__ != 'list': + zip_files[num_items - 1] = [zip_files[num_items-1]] + num_items -= 1 + else: + raise ValueError('File name arguments must be a list') + + class HandlerWrapper(MemcachedZipHandler): + """Simple wrapper for an instance of MemcachedZipHandler. + + I'm still not sure why this is needed + """ + + def get(self, name): + self.zipfilenames = zip_files + self.TrueGet(name) + if max_age is not None: + MAX_AGE = max_age + if public is not None: + PUBLIC = public + + return HandlerWrapper + + +class MemcachedZipHandler(webapp.RequestHandler): + """Handles get requests for a given URL. + + Serves a GET request from a series of zip files. As files are served they are + put into memcache, which is much faster than retreiving them from the zip + source file again. It also uses considerably fewer CPU cycles. + """ + zipfile_cache = {} # class cache of source zip files + MAX_AGE = 600 # max client-side cache lifetime + PUBLIC = True # public cache setting + CACHE_PREFIX = 'cache://' # memcache key prefix for actual URLs + NEG_CACHE_PREFIX = 'noncache://' # memcache key prefix for non-existant URL + + def TrueGet(self, name): + """The top-level entry point to serving requests. + + Called 'True' get because it does the work when called from the wrapper + class' get method + + Args: + name: URL requested + + Returns: + None + """ + name = self.PreprocessUrl(name) + + # see if we have the page in the memcache + resp_data = self.GetFromCache(name) + if resp_data is None: + logging.info('Cache miss for %s', name) + resp_data = self.GetFromNegativeCache(name) + if resp_data is None: + resp_data = self.GetFromStore(name) + + # IF we have the file, put it in the memcache + # ELSE put it in the negative cache + if resp_data is not None: + self.StoreOrUpdateInCache(name, resp_data) + else: + logging.info('Adding %s to negative cache, serving 404', name) + self.StoreInNegativeCache(name) + self.Write404Error() + return + else: + self.Write404Error() + return + + content_type, encoding = mimetypes.guess_type(name) + if content_type: + self.response.headers['Content-Type'] = content_type + self.SetCachingHeaders() + self.response.out.write(resp_data) + + def PreprocessUrl(self, name): + """Any preprocessing work on the URL when it comes it. + + Put any work related to interpretting the incoming URL here. For example, + this is used to redirect requests for a directory to the index.html file + in that directory. Subclasses should override this method to do different + preprocessing. + + Args: + name: The incoming URL + + Returns: + The processed URL + """ + # handle special case of requesting the domain itself + if not name: + name = 'index.html' + + # determine if this is a request for a directory + final_path_segment = name + final_slash_offset = name.rfind('/') + if final_slash_offset != len(name) - 1: + final_path_segment = name[final_slash_offset + 1:] + if final_path_segment.find('.') == -1: + name = ''.join([name, '/']) + + # if this is a directory, redirect to index.html + if name[len(name) - 1:] == '/': + return '%s%s' % (name, 'index.html') + else: + return name + + def GetFromStore(self, file_path): + """Retrieve file from zip files. + + Get the file from the source, it must not have been in the memcache. If + possible, we'll use the zip file index to quickly locate where the file + should be found. (See MapToFileArchive documentation for assumptions about + file ordering.) If we don't have an index or don't find the file where the + index says we should, look through all the zip files to find it. + + Args: + file_path: the file that we're looking for + + Returns: + The contents of the requested file + """ + resp_data = None + file_itr = iter(self.zipfilenames) + + # check the index, if we have one, to see what archive the file is in + archive_name = self.MapFileToArchive(file_path) + if not archive_name: + archive_name = file_itr.next()[0] + + while resp_data is None and archive_name: + zip_archive = self.LoadZipFile(archive_name) + if zip_archive: + + # we expect some lookups will fail, and that's okay, 404s will deal + # with that + try: + resp_data = zip_archive.read(file_path) + except (KeyError, RuntimeError), err: + # no op + x = False + if resp_data is not None: + logging.info('%s read from %s', file_path, archive_name) + + try: + archive_name = file_itr.next()[0] + except (StopIteration), err: + archive_name = False + + return resp_data + + def LoadZipFile(self, zipfilename): + """Convenience method to load zip file. + + Just a convenience method to load the zip file from the data store. This is + useful if we ever want to change data stores and also as a means of + dependency injection for testing. This method will look at our file cache + first, and then load and cache the file if there's a cache miss + + Args: + zipfilename: the name of the zip file to load + + Returns: + The zip file requested, or None if there is an I/O error + """ + zip_archive = None + zip_archive = self.zipfile_cache.get(zipfilename) + if zip_archive is None: + try: + zip_archive = zipfile.ZipFile(zipfilename) + self.zipfile_cache[zipfilename] = zip_archive + except (IOError, RuntimeError), err: + logging.error('Can\'t open zipfile %s, cause: %s' % (zipfilename, + err)) + return zip_archive + + def MapFileToArchive(self, file_path): + """Given a file name, determine what archive it should be in. + + This method makes two critical assumptions. + (1) The zip files passed as an argument to the handler, if concatenated + in that same order, would result in a total ordering + of all the files. See (2) for ordering type. + (2) Upper case letters before lower case letters. The traversal of a + directory tree is depth first. A parent directory's files are added + before the files of any child directories + + Args: + file_path: the file to be mapped to an archive + + Returns: + The name of the archive where we expect the file to be + """ + num_archives = len(self.zipfilenames) + while num_archives > 0: + target = self.zipfilenames[num_archives - 1] + if len(target) > 1: + if self.CompareFilenames(target[1], file_path) >= 0: + return target[0] + num_archives -= 1 + + return None + + def CompareFilenames(self, file1, file2): + """Determines whether file1 is lexigraphically 'before' file2. + + WARNING: This method assumes that paths are output in a depth-first, + with parent directories' files stored before childs' + + We say that file1 is lexigraphically before file2 if the last non-matching + path segment of file1 is alphabetically before file2. + + Args: + file1: the first file path + file2: the second file path + + Returns: + A positive number if file1 is before file2 + A negative number if file2 is before file1 + 0 if filenames are the same + """ + f1_segments = file1.split('/') + f2_segments = file2.split('/') + + segment_ptr = 0 + while (segment_ptr < len(f1_segments) and + segment_ptr < len(f2_segments) and + f1_segments[segment_ptr] == f2_segments[segment_ptr]): + segment_ptr += 1 + + if len(f1_segments) == len(f2_segments): + + # we fell off the end, the paths much be the same + if segment_ptr == len(f1_segments): + return 0 + + # we didn't fall of the end, compare the segments where they differ + if f1_segments[segment_ptr] < f2_segments[segment_ptr]: + return 1 + elif f1_segments[segment_ptr] > f2_segments[segment_ptr]: + return -1 + else: + return 0 + + # the number of segments differs, we either mismatched comparing + # directories, or comparing a file to a directory + else: + + # IF we were looking at the last segment of one of the paths, + # the one with fewer segments is first because files come before + # directories + # ELSE we just need to compare directory names + if (segment_ptr + 1 == len(f1_segments) or + segment_ptr + 1 == len(f2_segments)): + return len(f2_segments) - len(f1_segments) + else: + if f1_segments[segment_ptr] < f2_segments[segment_ptr]: + return 1 + elif f1_segments[segment_ptr] > f2_segments[segment_ptr]: + return -1 + else: + return 0 + + def SetCachingHeaders(self): + """Set caching headers for the request.""" + max_age = self.MAX_AGE + self.response.headers['Expires'] = email.Utils.formatdate( + time.time() + max_age, usegmt=True) + cache_control = [] + if self.PUBLIC: + cache_control.append('public') + cache_control.append('max-age=%d' % max_age) + self.response.headers['Cache-Control'] = ', '.join(cache_control) + + def GetFromCache(self, filename): + """Get file from memcache, if available. + + Args: + filename: The URL of the file to return + + Returns: + The content of the file + """ + return memcache.get('%s%s' % (self.CACHE_PREFIX, filename)) + + def StoreOrUpdateInCache(self, filename, data): + """Store data in the cache. + + Store a piece of data in the memcache. Memcache has a maximum item size of + 1*10^6 bytes. If the data is too large, fail, but log the failure. Future + work will consider compressing the data before storing or chunking it + + Args: + filename: the name of the file to store + data: the data of the file + + Returns: + None + """ + try: + if not memcache.add('%s%s' % (self.CACHE_PREFIX, filename), data): + memcache.replace('%s%s' % (self.CACHE_PREFIX, filename), data) + except (ValueError), err: + logging.warning('Data size too large to cache\n%s' % err) + + def Write404Error(self): + """Ouptut a simple 404 response.""" + self.error(404) + self.response.out.write( + ''.join(['<html><head><title>404: Not Found</title></head>', + '<body><b><h2>Error 404</h2><br/>', + 'File not found</b></body></html>'])) + + def StoreInNegativeCache(self, filename): + """If a non-existant URL is accessed, cache this result as well. + + Future work should consider setting a maximum negative cache size to + prevent it from from negatively impacting the real cache. + + Args: + filename: URL to add ot negative cache + + Returns: + None + """ + memcache.add('%s%s' % (self.NEG_CACHE_PREFIX, filename), -1) + + def GetFromNegativeCache(self, filename): + """Retrieve from negative cache. + + Args: + filename: URL to retreive + + Returns: + The file contents if present in the negative cache. + """ + return memcache.get('%s%s' % (self.NEG_CACHE_PREFIX, filename)) + + +def main(): + application = webapp.WSGIApplication([('/([^/]+)/(.*)', + MemcachedZipHandler)]) + util.run_wsgi_app(application) + + +if __name__ == '__main__': + main() diff --git a/scripts/combine_sdks.sh b/scripts/combine_sdks.sh index 89a1141..ebaa1c6 100755 --- a/scripts/combine_sdks.sh +++ b/scripts/combine_sdks.sh @@ -2,22 +2,36 @@ function replace() { - echo replacing $1 - rm -rf $UNZIPPED_BASE_DIR/$1 - cp -rf $UNZIPPED_IMAGE_DIR/$1 $UNZIPPED_BASE_DIR/$1 + echo replacing $1 + rm $V -rf "$UNZIPPED_BASE_DIR"/$1 + cp $V -rf "$UNZIPPED_IMAGE_DIR"/$1 "$UNZIPPED_BASE_DIR"/$1 } -BASE=$1 -IMAGES=$2 -OUTPUT=$3 +V="" +Q="-q" +if [ "$1" == "-v" ]; then + V="-v" + Q="" + shift +fi + +NOZIP="" +if [ "$1" == "-nozip" ]; then + NOZIP="1" + shift +fi -if [[ -z $BASE || -z $IMAGES || -z $OUTPUT ]] ; then - echo "usage: combine_sdks.sh BASE IMAGES OUTPUT" +BASE="$1" +IMAGES="$2" +OUTPUT="$3" + +if [[ -z "$BASE" || -z "$IMAGES" || -z "$OUTPUT" ]] ; then + echo "usage: combine_sdks.sh [-v] [-nozip] BASE IMAGES OUTPUT" echo echo " BASE and IMAGES should be sdk zip files. The system image files," echo " emulator and other runtime files will be copied from IMAGES and" echo " everything else will be copied from BASE. All of this will be" - echo " bundled into OUTPUT and zipped up again." + echo " bundled into OUTPUT and zipped up again (unless -nozip is specified)." echo exit 1 fi @@ -26,15 +40,25 @@ TMP=$(mktemp -d) TMP_ZIP=tmp.zip -BASE_DIR=$TMP/base -IMAGES_DIR=$TMP/images -OUTPUT_TMP_ZIP=$BASE_DIR/$TMP_ZIP +# determine executable extension +case `uname -s` in + *_NT-*) # for Windows + EXE=.exe + ;; + *) + EXE= + ;; +esac + +BASE_DIR="$TMP"/base +IMAGES_DIR="$TMP"/images +OUTPUT_TMP_ZIP="$BASE_DIR/$TMP_ZIP" -unzip -q $BASE -d $BASE_DIR -unzip -q $IMAGES -d $IMAGES_DIR +unzip $Q "$BASE" -d "$BASE_DIR" +unzip $Q "$IMAGES" -d "$IMAGES_DIR" -UNZIPPED_BASE_DIR=$(echo $BASE_DIR/*) -UNZIPPED_IMAGE_DIR=$(echo $IMAGES_DIR/*) +UNZIPPED_BASE_DIR=$(echo "$BASE_DIR"/*) +UNZIPPED_IMAGE_DIR=$(echo "$IMAGES_DIR"/*) # # The commands to copy over the files that we want @@ -42,21 +66,40 @@ UNZIPPED_IMAGE_DIR=$(echo $IMAGES_DIR/*) # replace tools/emulator # at this time we do not want the exe from SDK1.x replace tools/lib/images +replace tools/lib/res +replace tools/lib/fonts +replace tools/lib/layoutlib.jar replace docs replace android.jar +for i in widgets categories broadcast_actions service_actions; do + replace tools/lib/$i.txt +done + +if [ -d "$UNZIPPED_BASE_DIR"/usb_driver ]; then + replace usb_driver +fi + # # end # -pushd $BASE_DIR &> /dev/null - # rename the directory to the leaf minus the .zip of OUTPUT - LEAF=$(echo $OUTPUT | sed -e "s:.*\.zip/::" | sed -e "s:.zip$::") - mv * $LEAF - # zip it - zip -qr $TMP_ZIP $LEAF -popd &> /dev/null +if [ -z "$NOZIP" ]; then + pushd "$BASE_DIR" &> /dev/null + # rename the directory to the leaf minus the .zip of OUTPUT + LEAF=$(echo "$OUTPUT" | sed -e "s:.*\.zip/::" | sed -e "s:.zip$::") + mv * "$LEAF" + # zip it + zip $V -qr "$TMP_ZIP" "$LEAF" + popd &> /dev/null + + cp $V "$OUTPUT_TMP_ZIP" "$OUTPUT" + echo "Combined SDK available at $OUTPUT" +else + OUT_DIR="${OUTPUT//.zip/}" + mv $V "$BASE_DIR"/* "$OUT_DIR" + echo "Unzipped combined SDK available at $OUT_DIR" +fi -cp $OUTPUT_TMP_ZIP $OUTPUT +rm $V -rf "$TMP" -rm -rf $TMP diff --git a/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java index 2db668e..f1531e0 100644 --- a/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java +++ b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java @@ -31,52 +31,67 @@ import java.util.Map.Entry; * <li>define flags for your actions. * </ul> * <p/> - * To use, call {@link #parseArgs(String[])} and then call {@link #getValue(String, String)}. + * To use, call {@link #parseArgs(String[])} and then + * call {@link #getValue(String, String, String)}. */ public class CommandLineProcessor { - - /** Internal action name for all global flags. */ - public final static String GLOBAL_FLAG = "global"; - /** Internal action name for internally hidden flags. - * This is currently used to store the requested action name. */ - public final static String INTERNAL_FLAG = "internal"; + /** Internal verb name for internally hidden flags. */ + public final static String GLOBAL_FLAG_VERB = "@@internal@@"; + + /** String to use when the verb doesn't need any object. */ + public final static String NO_VERB_OBJECT = ""; + /** The global help flag. */ public static final String KEY_HELP = "help"; /** The global verbose flag. */ public static final String KEY_VERBOSE = "verbose"; /** The global silent flag. */ public static final String KEY_SILENT = "silent"; - /** The internal action flag. */ - public static final String KEY_ACTION = "action"; + + /** Verb requested by the user. Null if none specified, which will be an error. */ + private String mVerbRequested; + /** Direct object requested by the user. Can be null. */ + private String mDirectObjectRequested; - /** List of available actions. + /** + * Action definitions. * <p/> - * Each entry must be a 2-string array with first the action name and then - * a description. + * Each entry is a string array with: + * <ul> + * <li> the verb. + * <li> a direct object (use #NO_VERB_OBJECT if there's no object). + * <li> a description. + * <li> an alternate form for the object (e.g. plural). + * </ul> */ private final String[][] mActions; - /** The hash of all defined arguments. + + private static final int ACTION_VERB_INDEX = 0; + private static final int ACTION_OBJECT_INDEX = 1; + private static final int ACTION_DESC_INDEX = 2; + private static final int ACTION_ALT_OBJECT_INDEX = 3; + + /** + * The map of all defined arguments. * <p/> - * The key is a string "action/longName". + * The key is a string "verb/directObject/longName". */ private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>(); + /** Logger */ private final ISdkLog mLog; public CommandLineProcessor(ISdkLog logger, String[][] actions) { mLog = logger; mActions = actions; - define(MODE.STRING, false, INTERNAL_FLAG, null, KEY_ACTION, - "Selected Action", null); - - define(MODE.BOOLEAN, false, GLOBAL_FLAG, "v", KEY_VERBOSE, + define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE, "Verbose mode: errors, warnings and informational messages are printed.", false); - define(MODE.BOOLEAN, false, GLOBAL_FLAG, "s", KEY_SILENT, + define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT, "Silent mode: only errors are printed out.", false); - define(MODE.BOOLEAN, false, GLOBAL_FLAG, "h", KEY_HELP, + define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP, "This help.", false); } @@ -86,47 +101,54 @@ public class CommandLineProcessor { /** Helper that returns true if --verbose was requested. */ public boolean isVerbose() { - return ((Boolean) getValue(GLOBAL_FLAG, KEY_VERBOSE)).booleanValue(); + return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_VERBOSE)).booleanValue(); } /** Helper that returns true if --silent was requested. */ public boolean isSilent() { - return ((Boolean) getValue(GLOBAL_FLAG, KEY_SILENT)).booleanValue(); + return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_SILENT)).booleanValue(); } /** Helper that returns true if --help was requested. */ public boolean isHelpRequested() { - return ((Boolean) getValue(GLOBAL_FLAG, KEY_HELP)).booleanValue(); + return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_HELP)).booleanValue(); + } + + /** Returns the verb name from the command-line. Can be null. */ + public String getVerb() { + return mVerbRequested; } - /** Helper that returns the requested action name. */ - public String getActionRequested() { - return (String) getValue(INTERNAL_FLAG, KEY_ACTION); + /** Returns the direct object name from the command-line. Can be null. */ + public String getDirectObject() { + return mDirectObjectRequested; } //------------------ /** * Raw access to parsed parameter values. - * @param action The action name, including {@link #GLOBAL_FLAG} and {@link #INTERNAL_FLAG} + * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. + * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. * @param longFlagName The long flag name for the given action. * @return The current value object stored in the parameter, which depends on the argument mode. */ - public Object getValue(String action, String longFlagName) { - String key = action + "/" + longFlagName; + public Object getValue(String verb, String directObject, String longFlagName) { + String key = verb + "/" + directObject + "/" + longFlagName; Arg arg = mArguments.get(key); return arg.getCurrentValue(); } /** * Internal setter for raw parameter value. - * @param action The action name, including {@link #GLOBAL_FLAG} and {@link #INTERNAL_FLAG} + * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. + * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. * @param longFlagName The long flag name for the given action. * @param value The new current value object stored in the parameter, which depends on the * argument mode. */ - protected void setValue(String action, String longFlagName, Object value) { - String key = action + "/" + longFlagName; + protected void setValue(String verb, String directObject, String longFlagName, Object value) { + String key = verb + "/" + directObject + "/" + longFlagName; Arg arg = mArguments.get(key); arg.setCurrentValue(value); } @@ -140,114 +162,172 @@ public class CommandLineProcessor { */ public void parseArgs(String[] args) { String needsHelp = null; - String action = null; - - int n = args.length; - for (int i = 0; i < n; i++) { - Arg arg = null; - String a = args[i]; - if (a.startsWith("--")) { - arg = findLongArg(action, a.substring(2)); - } else if (a.startsWith("-")) { - arg = findShortArg(action, a.substring(1)); - } - - // Not a keyword and we don't have an action yet, this should be an action - if (arg == null && action == null) { - - if (a.startsWith("-")) { - // Got a keyword but not valid for global flags - needsHelp = String.format( - "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the action name?", - a, action); - break; - } - - for (String[] actionDesc : mActions) { - if (actionDesc[0].equals(a)) { - action = a; - break; - } - } - - if (action == null) { - needsHelp = String.format( - "Expected action name after global parameters but found %1$s instead.", - a); - break; + String verb = null; + String directObject = null; + + try { + int n = args.length; + for (int i = 0; i < n; i++) { + Arg arg = null; + String a = args[i]; + if (a.startsWith("--")) { + arg = findLongArg(verb, directObject, a.substring(2)); + } else if (a.startsWith("-")) { + arg = findShortArg(verb, directObject, a.substring(1)); } - } else if (arg == null && action != null) { - // Got a keyword but not valid for the current action - needsHelp = String.format( - "Flag '%1$s' is not valid for action '%2$s'.", - a, action); - break; - } else if (arg != null) { - // Process keyword - String error = null; - if (arg.getMode().needsExtra()) { - if (++i >= n) { - needsHelp = String.format("Missing argument for flag %1$s.", a); - break; + // No matching argument name found + if (arg == null) { + // Does it looks like a dashed parameter? + if (a.startsWith("-")) { + if (verb == null || directObject == null) { + // It looks like a dashed parameter and we don't have a a verb/object + // set yet, the parameter was just given too early. + + needsHelp = String.format( + "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the verb/object name?", + a); + return; + } else { + // It looks like a dashed parameter and but it is unknown by this + // verb-object combination + + needsHelp = String.format( + "Flag '%1$s' is not valid for '%2$s %3$s'.", + a, verb, directObject); + return; + } } - error = arg.getMode().process(arg, args[i]); - } else { - error = arg.getMode().process(arg, null); - - // If we just toggled help, we want to exit now without printing any error. - // We do this test here only when a Boolean flag is toggled since booleans - // are the only flags that don't take parameters and help is a boolean. - if (isHelpRequested()) { - printHelpAndExit(null); - // The call above should terminate however in unit tests we override - // it so we still need to return here. + if (verb == null) { + // Fill verb first. Find it. + for (String[] actionDesc : mActions) { + if (actionDesc[ACTION_VERB_INDEX].equals(a)) { + verb = a; + break; + } + } + + // Error if it was not a valid verb + if (verb == null) { + needsHelp = String.format( + "Expected verb after global parameters but found '%1$s' instead.", + a); + return; + } + + } else if (directObject == null) { + // Then fill the direct object. Find it. + for (String[] actionDesc : mActions) { + if (actionDesc[ACTION_VERB_INDEX].equals(verb)) { + if (actionDesc[ACTION_OBJECT_INDEX].equals(a)) { + directObject = a; + break; + } else if (actionDesc.length > ACTION_ALT_OBJECT_INDEX && + actionDesc[ACTION_ALT_OBJECT_INDEX].equals(a)) { + // if the alternate form exist and is used, we internally + // only memorize the default direct object form. + directObject = actionDesc[ACTION_OBJECT_INDEX]; + break; + } + } + } + + // Error if it was not a valid object for that verb + if (directObject == null) { + needsHelp = String.format( + "Expected verb after global parameters but found '%1$s' instead.", + a); + return; + + } + } + } else if (arg != null) { + // Process keyword + String error = null; + if (arg.getMode().needsExtra()) { + if (++i >= n) { + needsHelp = String.format("Missing argument for flag %1$s.", a); + return; + } + + error = arg.getMode().process(arg, args[i]); + } else { + error = arg.getMode().process(arg, null); + + // If we just toggled help, we want to exit now without printing any error. + // We do this test here only when a Boolean flag is toggled since booleans + // are the only flags that don't take parameters and help is a boolean. + if (isHelpRequested()) { + printHelpAndExit(null); + // The call above should terminate however in unit tests we override + // it so we still need to return here. + return; + } + } + + if (error != null) { + needsHelp = String.format("Invalid usage for flag %1$s: %2$s.", a, error); return; } } - - if (error != null) { - needsHelp = String.format("Invalid usage for flag %1$s: %2$s.", a, error); - break; - } } - } - if (needsHelp == null) { - if (action == null) { - needsHelp = "Missing action name."; - } else { - // Validate that all mandatory arguments are non-null for this action - String missing = null; - boolean plural = false; - for (Entry<String, Arg> entry : mArguments.entrySet()) { - Arg arg = entry.getValue(); - if (arg.getAction().equals(action)) { - if (arg.isMandatory() && arg.getCurrentValue() == null) { - if (missing == null) { - missing = "--" + arg.getLongArg(); - } else { - missing += ", --" + arg.getLongArg(); - plural = true; + if (needsHelp == null) { + if (verb == null) { + needsHelp = "Missing verb name."; + } else { + if (directObject == null) { + // Make sure this verb has an optional direct object + for (String[] actionDesc : mActions) { + if (actionDesc[ACTION_VERB_INDEX].equals(verb) && + actionDesc[ACTION_OBJECT_INDEX].equals(NO_VERB_OBJECT)) { + directObject = NO_VERB_OBJECT; + break; } } + + if (directObject == null) { + needsHelp = String.format("Missing object name for verb '%1$s'.", verb); + return; + } + } + + // Validate that all mandatory arguments are non-null for this action + String missing = null; + boolean plural = false; + for (Entry<String, Arg> entry : mArguments.entrySet()) { + Arg arg = entry.getValue(); + if (arg.getVerb().equals(verb) && + arg.getDirectObject().equals(directObject)) { + if (arg.isMandatory() && arg.getCurrentValue() == null) { + if (missing == null) { + missing = "--" + arg.getLongArg(); + } else { + missing += ", --" + arg.getLongArg(); + plural = true; + } + } + } + } + + if (missing != null) { + needsHelp = String.format( + "The %1$s %2$s must be defined for action '%3$s %4$s'", + plural ? "parameters" : "parameter", + missing, + verb, + directObject); } - } - if (missing != null) { - needsHelp = String.format("The %1$s %2$s must be defined for action '%3$s'", - plural ? "parameters" : "parameter", - missing, - action); + mVerbRequested = verb; + mDirectObjectRequested = directObject; } - - setValue(INTERNAL_FLAG, KEY_ACTION, action); } - } - - if (needsHelp != null) { - printHelpAndExitForAction(action, needsHelp); + } finally { + if (needsHelp != null) { + printHelpAndExitForAction(verb, directObject, needsHelp); + } } } @@ -255,11 +335,14 @@ public class CommandLineProcessor { * Finds an {@link Arg} given an action name and a long flag name. * @return The {@link Arg} found or null. */ - protected Arg findLongArg(String action, String longName) { - if (action == null) { - action = GLOBAL_FLAG; + protected Arg findLongArg(String verb, String directObject, String longName) { + if (verb == null) { + verb = GLOBAL_FLAG_VERB; + } + if (directObject == null) { + directObject = NO_VERB_OBJECT; } - String key = action + "/" + longName; + String key = verb + "/" + directObject + "/" + longName; return mArguments.get(key); } @@ -267,14 +350,17 @@ public class CommandLineProcessor { * Finds an {@link Arg} given an action name and a short flag name. * @return The {@link Arg} found or null. */ - protected Arg findShortArg(String action, String shortName) { - if (action == null) { - action = GLOBAL_FLAG; + protected Arg findShortArg(String verb, String directObject, String shortName) { + if (verb == null) { + verb = GLOBAL_FLAG_VERB; + } + if (directObject == null) { + directObject = NO_VERB_OBJECT; } for (Entry<String, Arg> entry : mArguments.entrySet()) { Arg arg = entry.getValue(); - if (arg.getAction().equals(action)) { + if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) { if (shortName.equals(arg.getShortArg())) { return arg; } @@ -291,18 +377,22 @@ public class CommandLineProcessor { * @param args Arguments for String.format */ public void printHelpAndExit(String errorFormat, Object... args) { - printHelpAndExitForAction(null /*actionFilter*/, errorFormat, args); + printHelpAndExitForAction(null /*verb*/, null /*directObject*/, errorFormat, args); } /** * Prints the help/usage and exits. * - * @param actionFilter If null, displays help for all actions. If not null, display help only - * for that specific action. In all cases also display general usage and action list. + * @param verb If null, displays help for all verbs. If not null, display help only + * for that specific verb. In all cases also displays general usage and action list. + * @param directObject If null, displays help for all verb objects. + * If not null, displays help only for that specific action + * In all cases also display general usage and action list. * @param errorFormat Optional error message to print prior to usage using String.format * @param args Arguments for String.format */ - public void printHelpAndExitForAction(String actionFilter, String errorFormat, Object... args) { + public void printHelpAndExitForAction(String verb, String directObject, + String errorFormat, Object... args) { if (errorFormat != null) { stderr(errorFormat, args); } @@ -316,25 +406,27 @@ public class CommandLineProcessor { " android [global options] action [action options]\n" + "\n" + "Global options:"); - listOptions(GLOBAL_FLAG); + listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT); - stdout("\nValid actions:"); + stdout("\nValid actions are composed of a verb and an optional direct object:"); for (String[] action : mActions) { - String filler = ""; - int len = action[0].length(); - if (len < 10) { - filler = " ".substring(len); - } - stdout("- %1$s:%2$s %3$s", action[0], filler, action[1]); + stdout("- %1$6s %2$-7s: %3$s", + action[ACTION_VERB_INDEX], + action[ACTION_OBJECT_INDEX], + action[ACTION_DESC_INDEX]); } for (String[] action : mActions) { - if (actionFilter == null || actionFilter.equals(action[0])) { - stdout("\nAction \"%1$s\":", action[0]); - stdout(" %1$s", action[1]); - stdout("Options:"); - listOptions(action[0]); + if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) { + if (directObject == null || directObject.equals(action[ACTION_OBJECT_INDEX])) { + stdout("\nAction \"%1$s %2$s\":", + action[ACTION_VERB_INDEX], + action[ACTION_OBJECT_INDEX]); + stdout(" %1$s", action[ACTION_DESC_INDEX]); + stdout("Options:"); + listOptions(action[ACTION_VERB_INDEX], action[ACTION_OBJECT_INDEX]); + } } } @@ -344,11 +436,11 @@ public class CommandLineProcessor { /** * Internal helper to print all the option flags for a given action name. */ - protected void listOptions(String action) { + protected void listOptions(String verb, String directObject) { int numOptions = 0; for (Entry<String, Arg> entry : mArguments.entrySet()) { Arg arg = entry.getValue(); - if (arg.getAction().equals(action)) { + if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) { String value = ""; if (arg.getDefaultValue() instanceof String[]) { @@ -483,21 +575,22 @@ public class CommandLineProcessor { * or a String array (in which case the first item is the current by default.) */ static class Arg { - private final String mAction; + private final String mVerb; + private final String mDirectObject; private final String mShortName; private final String mLongName; private final String mDescription; private final Object mDefaultValue; - private Object mCurrentValue; private final MODE mMode; private final boolean mMandatory; + private Object mCurrentValue; /** * Creates a new argument flag description. * * @param mode The {@link MODE} for the argument. * @param mandatory True if this argument is mandatory for this action. - * @param action The action name. Can be #GLOBAL_FLAG or #INTERNAL_FLAG. + * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG. * @param shortName The one-letter short argument name. Cannot be empty nor null. * @param longName The long argument name. Cannot be empty nor null. * @param description The description. Cannot be null. @@ -505,14 +598,16 @@ public class CommandLineProcessor { */ public Arg(MODE mode, boolean mandatory, - String action, + String verb, + String directObject, String shortName, String longName, String description, Object defaultValue) { mMode = mode; mMandatory = mandatory; - mAction = action; + mVerb = verb; + mDirectObject = directObject; mShortName = shortName; mLongName = longName; mDescription = description; @@ -540,8 +635,12 @@ public class CommandLineProcessor { return mDescription; } - public String getAction() { - return mAction; + public String getVerb() { + return mVerb; + } + + public String getDirectObject() { + return mDirectObject; } public Object getDefaultValue() { @@ -565,7 +664,8 @@ public class CommandLineProcessor { * Internal helper to define a new argument for a give action. * * @param mode The {@link MODE} for the argument. - * @param action The action name. Can be #GLOBAL_FLAG or #INTERNAL_FLAG. + * @param verb The verb name. Can be #INTERNAL_VERB. + * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG. * @param shortName The one-letter short argument name. Cannot be empty nor null. * @param longName The long argument name. Cannot be empty nor null. * @param description The description. Cannot be null. @@ -573,14 +673,19 @@ public class CommandLineProcessor { */ protected void define(MODE mode, boolean mandatory, - String action, + String verb, + String directObject, String shortName, String longName, String description, Object defaultValue) { assert(mandatory || mode == MODE.BOOLEAN); // a boolean mode cannot be mandatory - String key = action + "/" + longName; + if (directObject == null) { + directObject = NO_VERB_OBJECT; + } + + String key = verb + "/" + directObject + "/" + longName; mArguments.put(key, new Arg(mode, mandatory, - action, shortName, longName, description, defaultValue)); + verb, directObject, shortName, longName, description, defaultValue)); } /** diff --git a/sdkmanager/app/src/com/android/sdkmanager/Main.java b/sdkmanager/app/src/com/android/sdkmanager/Main.java index 1544f5b..7965e87 100644 --- a/sdkmanager/app/src/com/android/sdkmanager/Main.java +++ b/sdkmanager/app/src/com/android/sdkmanager/Main.java @@ -23,12 +23,12 @@ import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkManager; import com.android.sdklib.IAndroidTarget.IOptionalLibrary; +import com.android.sdklib.avd.AvdManager; +import com.android.sdklib.avd.HardwareProperties; +import com.android.sdklib.avd.AvdManager.AvdInfo; +import com.android.sdklib.avd.HardwareProperties.HardwareProperty; import com.android.sdklib.project.ProjectCreator; import com.android.sdklib.project.ProjectCreator.OutputLevel; -import com.android.sdklib.vm.HardwareProperties; -import com.android.sdklib.vm.VmManager; -import com.android.sdklib.vm.HardwareProperties.HardwareProperty; -import com.android.sdklib.vm.VmManager.VmInfo; import java.io.File; import java.io.IOException; @@ -56,8 +56,8 @@ class Main { private ISdkLog mSdkLog; /** The SDK manager parses the SDK folder and gives access to the content. */ private SdkManager mSdkManager; - /** Virtual Machine manager to access the list of VMs or create new ones. */ - private VmManager mVmManager; + /** Virtual Machine manager to access the list of AVDs or create new ones. */ + private AvdManager mAvdManager; /** Command-line processor with options specific to SdkManager. */ private SdkCommandLine mSdkCommandLine; /** The working directory, either null or set to an existing absolute canonical directory. */ @@ -183,26 +183,31 @@ class Main { * Actually do an action... */ private void doAction() { - String action = mSdkCommandLine.getActionRequested(); + String verb = mSdkCommandLine.getVerb(); + String directObject = mSdkCommandLine.getDirectObject(); - if (SdkCommandLine.ACTION_LIST.equals(action)) { + if (SdkCommandLine.VERB_LIST.equals(verb)) { // list action. - if (SdkCommandLine.ARG_TARGET.equals(mSdkCommandLine.getListFilter())) { + if (SdkCommandLine.OBJECT_TARGET.equals(directObject)) { displayTargetList(); - } else if (SdkCommandLine.ARG_VM.equals(mSdkCommandLine.getListFilter())) { - displayVmList(); + } else if (SdkCommandLine.OBJECT_AVD.equals(directObject)) { + displayAvdList(); } else { displayTargetList(); - displayVmList(); + displayAvdList(); } - } else if (SdkCommandLine.ACTION_NEW_VM.equals(action)) { - createVm(); - } else if (SdkCommandLine.ACTION_NEW_PROJECT.equals(action)) { + + } else if (SdkCommandLine.VERB_CREATE.equals(verb) && + SdkCommandLine.OBJECT_AVD.equals(directObject)) { + createAvd(); + + } else if (SdkCommandLine.VERB_CREATE.equals(verb) && + SdkCommandLine.OBJECT_PROJECT.equals(directObject)) { // get the target and try to resolve it. - int targetId = mSdkCommandLine.getNewProjectTargetId(); + int targetId = mSdkCommandLine.getCreateProjectTargetId(); IAndroidTarget[] targets = mSdkManager.getTargets(); if (targetId < 1 || targetId > targets.length) { - errorAndExit("Target id is not valid. Use '%s list -f target' to get the target Ids.", + errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.", SdkConstants.androidCmdName()); } IAndroidTarget target = targets[targetId - 1]; @@ -213,22 +218,24 @@ class Main { OutputLevel.NORMAL, mSdkLog); - String projectDir = getProjectLocation(mSdkCommandLine.getNewProjectLocation()); + String projectDir = getProjectLocation(mSdkCommandLine.getCreateProjectLocation()); creator.createProject(projectDir, - mSdkCommandLine.getNewProjectName(), - mSdkCommandLine.getNewProjectPackage(), - mSdkCommandLine.getNewProjectActivity(), + mSdkCommandLine.getCreateProjectName(), + mSdkCommandLine.getCreateProjectPackage(), + mSdkCommandLine.getCreateProjectActivity(), target, false /* isTestProject*/); - } else if (SdkCommandLine.ACTION_UPDATE_PROJECT.equals(action)) { + + } else if (SdkCommandLine.VERB_UPDATE.equals(verb) && + SdkCommandLine.OBJECT_PROJECT.equals(directObject)) { // get the target and try to resolve it. IAndroidTarget target = null; int targetId = mSdkCommandLine.getUpdateProjectTargetId(); if (targetId >= 0) { IAndroidTarget[] targets = mSdkManager.getTargets(); if (targetId < 1 || targetId > targets.length) { - errorAndExit("Target id is not valid. Use '%s list -f target' to get the target Ids.", + errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.", SdkConstants.androidCmdName()); } target = targets[targetId - 1]; @@ -340,20 +347,20 @@ class Main { } /** - * Displays the list of available VMs. + * Displays the list of available AVDs. */ - private void displayVmList() { + private void displayAvdList() { try { - mVmManager = new VmManager(mSdkManager, null /* sdklog */); + mAvdManager = new AvdManager(mSdkManager, null /* sdklog */); - mSdkLog.printf("Available Android VMs:\n"); + mSdkLog.printf("Available Android Virtual Devices:\n"); int index = 1; - for (VmInfo info : mVmManager.getVms()) { + for (AvdInfo info : mAvdManager.getAvds()) { mSdkLog.printf("[%d] %s\n", index, info.getName()); mSdkLog.printf(" Path: %s\n", info.getPath()); - // get the target of the Vm + // get the target of the AVD IAndroidTarget target = info.getTarget(); if (target.isPlatform()) { mSdkLog.printf(" Target: %s (API level %d)\n", target.getName(), @@ -373,57 +380,85 @@ class Main { } /** - * Creates a new VM. This is a text based creation with command line prompt. + * Creates a new AVD. This is a text based creation with command line prompt. */ - private void createVm() { + private void createAvd() { // find a matching target - int targetId = mSdkCommandLine.getNewVmTargetId(); + int targetId = mSdkCommandLine.getCreateAvdTargetId(); IAndroidTarget target = null; if (targetId >= 1 && targetId <= mSdkManager.getTargets().length) { target = mSdkManager.getTargets()[targetId-1]; // target it is 1-based } else { - errorAndExit("Target id is not valid. Use '%s list -f target' to get the target Ids.", + errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.", SdkConstants.androidCmdName()); } try { - mVmManager = new VmManager(mSdkManager, mSdkLog); + boolean removePrevious = false; + mAvdManager = new AvdManager(mSdkManager, mSdkLog); - String vmName = mSdkCommandLine.getNewVmName(); - VmInfo info = mVmManager.getVm(vmName); + String avdName = mSdkCommandLine.getCreateAvdName(); + AvdInfo info = mAvdManager.getAvd(avdName); if (info != null) { - errorAndExit("VM %s already exists.", vmName); - } else { - String vmParentFolder = mSdkCommandLine.getNewVmLocation(); - if (vmParentFolder == null) { - vmParentFolder = AndroidLocation.getFolder() + AndroidLocation.FOLDER_VMS; + if (mSdkCommandLine.getCreateAvdForce()) { + removePrevious = true; + mSdkLog.warning( + "Android Virtual Device '%s' already exists and will be replaced.", + avdName); + } else { + errorAndExit("Android Virtual Device '%s' already exists.", avdName); + return; } + } - Map<String, String> hardwareConfig = null; - if (target.isPlatform()) { - try { - hardwareConfig = promptForHardware(target); - } catch (IOException e) { - errorAndExit(e.getMessage()); - } + String avdParentFolder = mSdkCommandLine.getCreateAvdLocation(); + if (avdParentFolder == null) { + avdParentFolder = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD; + } + + Map<String, String> hardwareConfig = null; + if (target.isPlatform()) { + try { + hardwareConfig = promptForHardware(target); + } catch (IOException e) { + errorAndExit(e.getMessage()); } + } + + AvdInfo oldAvdInfo = null; + if (removePrevious) { + oldAvdInfo = mAvdManager.getAvd(avdName); + } - mVmManager.createVm(vmParentFolder, - mSdkCommandLine.getNewVmName(), - target, - mSdkCommandLine.getNewVmSkin(), - mSdkCommandLine.getNewVmSdCard(), - hardwareConfig, - mSdkLog); + AvdInfo newAvdInfo = mAvdManager.createAvd(avdParentFolder, + avdName, + target, + mSdkCommandLine.getCreateAvdSkin(), + mSdkCommandLine.getCreateAvdSdCard(), + hardwareConfig, + removePrevious, + mSdkLog); + + if (newAvdInfo != null && + oldAvdInfo != null && + !oldAvdInfo.getPath().equals(newAvdInfo.getPath())) { + mSdkLog.warning("Removing previous AVD directory at %s", oldAvdInfo.getPath()); + // Remove the old data directory + File dir = new File(oldAvdInfo.getPath()); + mAvdManager.recursiveDelete(dir); + dir.delete(); + // Remove old avd info from manager + mAvdManager.removeAvd(oldAvdInfo); } + } catch (AndroidLocationException e) { errorAndExit(e.getMessage()); } } /** - * Prompts the user to setup a hardware config for a Platform-based VM. + * Prompts the user to setup a hardware config for a Platform-based AVD. * @throws IOException */ private Map<String, String> promptForHardware(IAndroidTarget createTarget) throws IOException { diff --git a/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java index 08626a8..fe93396 100644 --- a/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java +++ b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java @@ -25,152 +25,218 @@ import com.android.sdklib.SdkManager; */ public class SdkCommandLine extends CommandLineProcessor { - public static final String ARG_ALIAS = "alias"; + public final static String VERB_LIST = "list"; + public final static String VERB_CREATE = "create"; + public final static String VERB_RENAME = "rename"; + public final static String VERB_MOVE = "move"; + public final static String VERB_DELETE = "delete"; + public final static String VERB_UPDATE = "update"; + + public static final String OBJECT_AVD = "avd"; + public static final String OBJECT_AVDS = "avds"; + public static final String OBJECT_TARGET = "target"; + public static final String OBJECT_TARGETS = "targets"; + public static final String OBJECT_PROJECT = "project"; + + public static final String ARG_ALIAS = "alias"; public static final String ARG_ACTIVITY = "activity"; - public static final String ARG_VM = "vm"; - public static final String ARG_TARGET = "target"; - public static final String ARG_ALL = "all"; + + public static final String KEY_ACTIVITY = ARG_ACTIVITY; + public static final String KEY_PACKAGE = "package"; + public static final String KEY_MODE = "mode"; + public static final String KEY_TARGET_ID = OBJECT_TARGET; + public static final String KEY_NAME = "name"; + public static final String KEY_PATH = "path"; + public static final String KEY_FILTER = "filter"; + public static final String KEY_SKIN = "skin"; + public static final String KEY_SDCARD = "sdcard"; + public static final String KEY_FORCE = "force"; + + /** + * Action definitions for SdkManager command line. + * <p/> + * Each entry is a string array with: + * <ul> + * <li> the verb. + * <li> an object (use #NO_VERB_OBJECT if there's no object). + * <li> a description. + * <li> an alternate form for the object (e.g. plural). + * </ul> + */ + private final static String[][] ACTIONS = { + { VERB_LIST, + NO_VERB_OBJECT, + "Lists existing targets or virtual devices." }, + { VERB_LIST, + OBJECT_AVD, + "Lists existing Android Virtual Devices.", + OBJECT_AVDS }, + { VERB_LIST, + OBJECT_TARGET, + "Lists existing targets.", + OBJECT_TARGETS }, - public static final String KEY_ACTIVITY = ARG_ACTIVITY; - public static final String KEY_PACKAGE = "package"; - public static final String KEY_MODE = "mode"; - public static final String KEY_TARGET_ID = ARG_TARGET; - public static final String KEY_NAME = "name"; - public static final String KEY_OUT = "out"; - public static final String KEY_FILTER = "filter"; - public static final String KEY_SKIN = "skin"; - public static final String KEY_SDCARD = "sdcard"; - - public final static String ACTION_LIST = "list"; - public final static String ACTION_NEW_VM = ARG_VM; - public final static String ACTION_NEW_PROJECT = "project"; - public final static String ACTION_UPDATE_PROJECT = "update"; + { VERB_CREATE, + OBJECT_AVD, + "Creates a new Android Virtual Device." }, + { VERB_RENAME, + OBJECT_AVD, + "Renames a new Android Virtual Device." }, + { VERB_MOVE, + OBJECT_AVD, + "Moves a new Android Virtual Device." }, + { VERB_DELETE, + OBJECT_AVD, + "Deletes a new Android Virtual Device." }, - private final static String[][] ACTIONS = { - { ACTION_LIST, - "Lists existing targets or VMs." }, - { ACTION_NEW_VM, - "Creates a new VM." }, - { ACTION_NEW_PROJECT, - "Creates a new project using a template." }, - { ACTION_UPDATE_PROJECT, - "Updates a project from existing source (must have an AndroidManifest.xml)." }, + { VERB_CREATE, + OBJECT_PROJECT, + "Creates a new Android Project." }, + { VERB_UPDATE, + OBJECT_PROJECT, + "Updates an Android Project (must have an AndroidManifest.xml)." }, }; public SdkCommandLine(ISdkLog logger) { super(logger, ACTIONS); - define(MODE.ENUM, false, ACTION_LIST, "f", KEY_FILTER, - "List filter", new String[] { ARG_ALL, ARG_TARGET, ARG_VM }); - - define(MODE.STRING, false, ACTION_NEW_VM, "o", KEY_OUT, - "Location path of new VM", null); - define(MODE.STRING, true, ACTION_NEW_VM, "n", KEY_NAME, - "Name of the new VM", null); - define(MODE.INTEGER, true, ACTION_NEW_VM, "t", KEY_TARGET_ID, - "Target id of the new VM", null); - define(MODE.STRING, true, ACTION_NEW_VM, "s", KEY_SKIN, - "Skin of the new VM", null); - define(MODE.STRING, false, ACTION_NEW_VM, "c", KEY_SDCARD, - "Path to a shared SD card image, or size of a new sdcard for the new VM", null); - - define(MODE.ENUM, true, ACTION_NEW_PROJECT, "m", KEY_MODE, + define(MODE.STRING, false, + VERB_CREATE, OBJECT_AVD, + "p", KEY_PATH, + "Location path of the parent directory where the new AVD will be created", null); + define(MODE.STRING, true, + VERB_CREATE, OBJECT_AVD, + "n", KEY_NAME, + "Name of the new AVD", null); + define(MODE.INTEGER, true, + VERB_CREATE, OBJECT_AVD, + "t", KEY_TARGET_ID, + "Target id of the new AVD", null); + define(MODE.STRING, true, + VERB_CREATE, OBJECT_AVD, + "s", KEY_SKIN, + "Skin of the new AVD", null); + define(MODE.STRING, false, + VERB_CREATE, OBJECT_AVD, + "c", KEY_SDCARD, + "Path to a shared SD card image, or size of a new sdcard for the new AVD", null); + define(MODE.BOOLEAN, false, + VERB_CREATE, OBJECT_AVD, + "f", KEY_FORCE, + "Force creation (override an existing AVD)", false); + + define(MODE.ENUM, true, + VERB_CREATE, OBJECT_PROJECT, + "m", KEY_MODE, "Project mode", new String[] { ARG_ACTIVITY, ARG_ALIAS }); - define(MODE.STRING, true, ACTION_NEW_PROJECT, "o", KEY_OUT, + define(MODE.STRING, true, + VERB_CREATE, OBJECT_PROJECT, + "p", KEY_PATH, "Location path of new project", null); - define(MODE.INTEGER, true, ACTION_NEW_PROJECT, "t", KEY_TARGET_ID, + define(MODE.INTEGER, true, + VERB_CREATE, OBJECT_PROJECT, + "t", KEY_TARGET_ID, "Target id of the new project", null); - define(MODE.STRING, true, ACTION_NEW_PROJECT, "p", KEY_PACKAGE, + define(MODE.STRING, true, + VERB_CREATE, OBJECT_PROJECT, + "k", KEY_PACKAGE, "Package name", null); - define(MODE.STRING, true, ACTION_NEW_PROJECT, "a", KEY_ACTIVITY, + define(MODE.STRING, true, + VERB_CREATE, OBJECT_PROJECT, + "a", KEY_ACTIVITY, "Activity name", null); - define(MODE.STRING, false, ACTION_NEW_PROJECT, "n", KEY_NAME, + define(MODE.STRING, false, + VERB_CREATE, OBJECT_PROJECT, + "n", KEY_NAME, "Project name", null); - define(MODE.STRING, true, ACTION_UPDATE_PROJECT, "o", KEY_OUT, + define(MODE.STRING, true, + VERB_UPDATE, OBJECT_PROJECT, + "p", KEY_PATH, "Location path of the project", null); - define(MODE.INTEGER, true, ACTION_UPDATE_PROJECT, "t", KEY_TARGET_ID, + define(MODE.INTEGER, true, + VERB_UPDATE, OBJECT_PROJECT, + "t", KEY_TARGET_ID, "Target id to set for the project", -1); - define(MODE.STRING, false, ACTION_UPDATE_PROJECT, "n", KEY_NAME, + define(MODE.STRING, false, + VERB_UPDATE, OBJECT_PROJECT, + "n", KEY_NAME, "Project name", null); } - // -- some helpers for list action flags - - /** Helper to retrieve the --filter for the list action. */ - public String getListFilter() { - return ((String) getValue(ACTION_LIST, KEY_FILTER)); - } - - // -- some helpers for vm action flags + // -- some helpers for AVD action flags - /** Helper to retrieve the --out location for the new vm action. */ - public String getNewVmLocation() { - return ((String) getValue(ACTION_NEW_VM, KEY_OUT)); + /** Helper to retrieve the --out location for the new AVD action. */ + public String getCreateAvdLocation() { + return ((String) getValue(VERB_CREATE, OBJECT_AVD, KEY_PATH)); } - /** Helper to retrieve the --target id for the new vm action. */ - public int getNewVmTargetId() { - return ((Integer) getValue(ACTION_NEW_VM, KEY_TARGET_ID)).intValue(); + /** Helper to retrieve the --target id for the new AVD action. */ + public int getCreateAvdTargetId() { + return ((Integer) getValue(VERB_CREATE, OBJECT_AVD, KEY_TARGET_ID)).intValue(); } - /** Helper to retrieve the --name for the new vm action. */ - public String getNewVmName() { - return ((String) getValue(ACTION_NEW_VM, KEY_NAME)); + /** Helper to retrieve the --name for the new AVD action. */ + public String getCreateAvdName() { + return ((String) getValue(VERB_CREATE, OBJECT_AVD, KEY_NAME)); } - /** Helper to retrieve the --skin name for the new vm action. */ - public String getNewVmSkin() { - return ((String) getValue(ACTION_NEW_VM, KEY_SKIN)); + /** Helper to retrieve the --skin name for the new AVD action. */ + public String getCreateAvdSkin() { + return ((String) getValue(VERB_CREATE, OBJECT_AVD, KEY_SKIN)); } - /** Helper to retrieve the --sdcard data for the new vm action. */ - public String getNewVmSdCard() { - return ((String) getValue(ACTION_NEW_VM, KEY_SDCARD)); + /** Helper to retrieve the --sdcard data for the new AVD action. */ + public String getCreateAvdSdCard() { + return ((String) getValue(VERB_CREATE, OBJECT_AVD, KEY_SDCARD)); + } + + public boolean getCreateAvdForce() { + return ((Boolean) getValue(VERB_CREATE, OBJECT_AVD, KEY_FORCE)).booleanValue(); } // -- some helpers for project action flags /** Helper to retrieve the --out location for the new project action. */ - public String getNewProjectLocation() { - return ((String) getValue(ACTION_NEW_PROJECT, KEY_OUT)); + public String getCreateProjectLocation() { + return ((String) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_PATH)); } /** Helper to retrieve the --target id for the new project action. */ - public int getNewProjectTargetId() { - return ((Integer) getValue(ACTION_NEW_PROJECT, KEY_TARGET_ID)).intValue(); + public int getCreateProjectTargetId() { + return ((Integer) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_TARGET_ID)).intValue(); } /** Helper to retrieve the --name for the new project action. */ - public String getNewProjectName() { - return ((String) getValue(ACTION_NEW_PROJECT, KEY_NAME)); + public String getCreateProjectName() { + return ((String) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_NAME)); } /** Helper to retrieve the --package for the new project action. */ - public String getNewProjectPackage() { - return ((String) getValue(ACTION_NEW_PROJECT, KEY_PACKAGE)); + public String getCreateProjectPackage() { + return ((String) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_PACKAGE)); } /** Helper to retrieve the --activity for the new project action. */ - public String getNewProjectActivity() { - return ((String) getValue(ACTION_NEW_PROJECT, KEY_ACTIVITY)); + public String getCreateProjectActivity() { + return ((String) getValue(VERB_CREATE, OBJECT_PROJECT, KEY_ACTIVITY)); } // -- some helpers for update action flags /** Helper to retrieve the --out location for the update project action. */ public String getUpdateProjectLocation() { - return ((String) getValue(ACTION_UPDATE_PROJECT, KEY_OUT)); + return ((String) getValue(VERB_UPDATE, OBJECT_PROJECT, KEY_PATH)); } /** Helper to retrieve the --target id for the update project action. */ public int getUpdateProjectTargetId() { - return ((Integer) getValue(ACTION_UPDATE_PROJECT, KEY_TARGET_ID)).intValue(); + return ((Integer) getValue(VERB_UPDATE, OBJECT_PROJECT, KEY_TARGET_ID)).intValue(); } /** Helper to retrieve the --name for the update project action. */ public String getUpdateProjectName() { - return ((String) getValue(ACTION_UPDATE_PROJECT, KEY_NAME)); + return ((String) getValue(VERB_UPDATE, OBJECT_PROJECT, KEY_NAME)); } } diff --git a/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java b/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java index 1a82151..918591b 100644 --- a/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java +++ b/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java @@ -38,20 +38,20 @@ public class CommandLineProcessorTest extends TestCase { public MockCommandLineProcessor(ISdkLog logger) { super(logger, new String[][] { - { "action1", "Some action" }, - { "action2", "Another action" }, + { "verb1", "action1", "Some action" }, + { "verb1", "action2", "Another action" }, }); define(MODE.STRING, false /*mandatory*/, - "action1", "1", "first", "non-mandatory flag", null); + "verb1", "action1", "1", "first", "non-mandatory flag", null); define(MODE.STRING, true /*mandatory*/, - "action1", "2", "second", "mandatory flag", null); + "verb1", "action1", "2", "second", "mandatory flag", null); } @Override - public void printHelpAndExitForAction(String actionFilter, + public void printHelpAndExitForAction(String verb, String directObject, String errorFormat, Object... args) { mHelpCalled = true; - super.printHelpAndExitForAction(actionFilter, errorFormat, args); + super.printHelpAndExitForAction(verb, directObject, errorFormat, args); } @Override @@ -132,14 +132,14 @@ public class CommandLineProcessorTest extends TestCase { assertTrue(c.isVerbose()); assertTrue(c.wasExitCalled()); assertTrue(c.wasHelpCalled()); - assertTrue(c.getStdErr().indexOf("Missing action name.") != -1); + assertTrue(c.getStdErr().indexOf("Missing verb name.") != -1); c = new MockCommandLineProcessor(mLog); c.parseArgs(new String[] { "--verbose" }); assertTrue(c.isVerbose()); assertTrue(c.wasExitCalled()); assertTrue(c.wasHelpCalled()); - assertTrue(c.getStdErr().indexOf("Missing action name.") != -1); + assertTrue(c.getStdErr().indexOf("Missing verb name.") != -1); } public final void testHelp() { @@ -148,39 +148,39 @@ public class CommandLineProcessorTest extends TestCase { c.parseArgs(new String[] { "-h" }); assertTrue(c.wasExitCalled()); assertTrue(c.wasHelpCalled()); - assertTrue(c.getStdErr().indexOf("Missing action name.") == -1); + assertTrue(c.getStdErr().indexOf("Missing verb name.") == -1); c = new MockCommandLineProcessor(mLog); c.parseArgs(new String[] { "--help" }); assertTrue(c.wasExitCalled()); assertTrue(c.wasHelpCalled()); - assertTrue(c.getStdErr().indexOf("Missing action name.") == -1); + assertTrue(c.getStdErr().indexOf("Missing verb name.") == -1); } public final void testMandatory() { MockCommandLineProcessor c = new MockCommandLineProcessor(mLog); - c.parseArgs(new String[] { "action1", "-1", "value1", "-2", "value2" }); + c.parseArgs(new String[] { "verb1", "action1", "-1", "value1", "-2", "value2" }); assertFalse(c.wasExitCalled()); assertFalse(c.wasHelpCalled()); assertEquals("", c.getStdErr()); - assertEquals("value1", c.getValue("action1", "first")); - assertEquals("value2", c.getValue("action1", "second")); + assertEquals("value1", c.getValue("verb1", "action1", "first")); + assertEquals("value2", c.getValue("verb1", "action1", "second")); c = new MockCommandLineProcessor(mLog); - c.parseArgs(new String[] { "action1", "-2", "value2" }); + c.parseArgs(new String[] { "verb1", "action1", "-2", "value2" }); assertFalse(c.wasExitCalled()); assertFalse(c.wasHelpCalled()); assertEquals("", c.getStdErr()); - assertEquals(null, c.getValue("action1", "first")); - assertEquals("value2", c.getValue("action1", "second")); + assertEquals(null, c.getValue("verb1", "action1", "first")); + assertEquals("value2", c.getValue("verb1", "action1", "second")); c = new MockCommandLineProcessor(mLog); - c.parseArgs(new String[] { "action1" }); + c.parseArgs(new String[] { "verb1", "action1" }); assertTrue(c.wasExitCalled()); assertTrue(c.wasHelpCalled()); assertTrue(c.getStdErr().indexOf("must be defined") != -1); - assertEquals(null, c.getValue("action1", "first")); - assertEquals(null, c.getValue("action1", "second")); + assertEquals(null, c.getValue("verb1", "action1", "first")); + assertEquals(null, c.getValue("verb1", "action1", "second")); } } diff --git a/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java b/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java index 5a2c8e1..07a32e0 100644 --- a/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java +++ b/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java @@ -37,10 +37,10 @@ public class SdkCommandLineTest extends TestCase { } @Override - public void printHelpAndExitForAction(String actionFilter, + public void printHelpAndExitForAction(String verb, String directObject, String errorFormat, Object... args) { mHelpCalled = true; - super.printHelpAndExitForAction(actionFilter, errorFormat, args); + super.printHelpAndExitForAction(verb, directObject, errorFormat, args); } @Override @@ -78,34 +78,64 @@ public class SdkCommandLineTest extends TestCase { super.tearDown(); } - /** Test list with long name and verbose */ - public final void testList_Long_Verbose() { + /** Test list */ + public final void testList_Avd_Verbose() { MockSdkCommandLine c = new MockSdkCommandLine(mLog); - assertEquals("all", c.getListFilter()); - c.parseArgs(new String[] { "-v", "list", "--filter", "vm" }); + c.parseArgs(new String[] { "-v", "list", "avd" }); assertFalse(c.wasHelpCalled()); assertFalse(c.wasExitCalled()); - assertEquals("vm", c.getListFilter()); + assertEquals("list", c.getVerb()); + assertEquals("avd", c.getDirectObject()); assertTrue(c.isVerbose()); } - /** Test list with short name and no verbose */ - public final void testList_Short() { + public final void testList_Target() { MockSdkCommandLine c = new MockSdkCommandLine(mLog); - assertEquals("all", c.getListFilter()); - c.parseArgs(new String[] { "list", "-f", "vm" }); + c.parseArgs(new String[] { "list", "target" }); assertFalse(c.wasHelpCalled()); assertFalse(c.wasExitCalled()); - assertEquals("vm", c.getListFilter()); + assertEquals("list", c.getVerb()); + assertEquals("target", c.getDirectObject()); + assertFalse(c.isVerbose()); } - - /** Test list with long name and missing parameter */ - public final void testList_Long_MissingParam() { + + public final void testList_None() { + MockSdkCommandLine c = new MockSdkCommandLine(mLog); + c.parseArgs(new String[] { "list" }); + assertFalse(c.wasHelpCalled()); + assertFalse(c.wasExitCalled()); + assertEquals("list", c.getVerb()); + assertEquals("", c.getDirectObject()); + assertFalse(c.isVerbose()); + } + + public final void testList_Invalid() { MockSdkCommandLine c = new MockSdkCommandLine(mLog); - assertEquals("all", c.getListFilter()); - c.parseArgs(new String[] { "list", "--filter" }); + c.parseArgs(new String[] { "list", "unknown" }); assertTrue(c.wasHelpCalled()); assertTrue(c.wasExitCalled()); - assertEquals("all", c.getListFilter()); + assertEquals(null, c.getVerb()); + assertEquals(null, c.getDirectObject()); + assertFalse(c.isVerbose()); + } + + public final void testList_Plural() { + MockSdkCommandLine c = new MockSdkCommandLine(mLog); + c.parseArgs(new String[] { "list", "avds" }); + assertFalse(c.wasHelpCalled()); + assertFalse(c.wasExitCalled()); + assertEquals("list", c.getVerb()); + // we get the non-plural form + assertEquals("avd", c.getDirectObject()); + assertFalse(c.isVerbose()); + + c = new MockSdkCommandLine(mLog); + c.parseArgs(new String[] { "list", "targets" }); + assertFalse(c.wasHelpCalled()); + assertFalse(c.wasExitCalled()); + assertEquals("list", c.getVerb()); + // we get the non-plural form + assertEquals("target", c.getDirectObject()); + assertFalse(c.isVerbose()); } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java index 8cbe44a..4894517 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/ISdkLog.java @@ -16,11 +16,48 @@ package com.android.sdklib; +import java.util.Formatter; + /** * Interface used to display warnings/errors while parsing the SDK content. */ public interface ISdkLog { + + /** + * Prints a warning message on stdout. + * <p/> + * Implementations should only display warnings in verbose mode. + * The message should be prefixed with "Warning:". + * + * @param warningFormat is an optional error format. If non-null, it will be printed + * using a {@link Formatter} with the provided arguments. + * @param args provides the arguments for warningFormat. + */ void warning(String warningFormat, Object... args); + + /** + * Prints an error message on stderr. + * <p/> + * Implementation should always display errors, independent of verbose mode. + * The message should be prefixed with "Error:". + * + * @param t is an optional {@link Throwable} or {@link Exception}. If non-null, it's + * message will be printed out. + * @param errorFormat is an optional error format. If non-null, it will be printed + * using a {@link Formatter} with the provided arguments. + * @param args provides the arguments for errorFormat. + */ void error(Throwable t, String errorFormat, Object... args); + + /** + * Prints a message as-is on stdout. + * <p/> + * Implementation should always display errors, independent of verbose mode. + * No prefix is used, the message is printed as-is after formatting. + * + * @param msgFormat is an optional error format. If non-null, it will be printed + * using a {@link Formatter} with the provided arguments. + * @param args provides the arguments for msgFormat. + */ void printf(String msgFormat, Object... args); } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/VmManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java index 39316d2..a43b8e6 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/VmManager.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.sdklib.vm; +package com.android.sdklib.avd; import com.android.prefs.AndroidLocation; import com.android.prefs.AndroidLocation.AndroidLocationException; @@ -39,12 +39,13 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * Virtual Machine manager to access the list of VMs or create new ones. + * Virtual Device Manager to access the list of AVDs or create new ones. */ -public final class VmManager { +public final class AvdManager { - private final static String VM_INFO_PATH = "path"; - private final static String VM_INFO_TARGET = "target"; + private static final String AVD_FOLDER_EXTENSION = ".avd"; + private final static String AVD_INFO_PATH = "path"; + private final static String AVD_INFO_TARGET = "target"; private final static String IMAGE_USERDATA = "userdata.img"; private final static String CONFIG_INI = "config.ini"; @@ -54,7 +55,7 @@ public final class VmManager { private final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("\\d+[MK]?"); - public static final class VmInfo { + public static final class AvdInfo { String name; String path; IAndroidTarget target; @@ -72,30 +73,30 @@ public final class VmManager { } } - private final ArrayList<VmInfo> mVmList = new ArrayList<VmInfo>(); + private final ArrayList<AvdInfo> mAvdList = new ArrayList<AvdInfo>(); private ISdkLog mSdkLog; private final SdkManager mSdk; - public VmManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException { + public AvdManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException { mSdk = sdk; mSdkLog = sdkLog; - buildVmList(); + buildAvdList(); } /** - * Returns the existing VMs. - * @return a newly allocated arrays containing all the VMs. + * Returns the existing AVDs. + * @return a newly allocated array containing all the AVDs. */ - public VmInfo[] getVms() { - return mVmList.toArray(new VmInfo[mVmList.size()]); + public AvdInfo[] getAvds() { + return mAvdList.toArray(new AvdInfo[mAvdList.size()]); } /** - * Returns the {@link VmInfo} matching the given <var>name</var>. - * @return the matching VmInfo or <code>null</code> if none were found. + * Returns the {@link AvdInfo} matching the given <var>name</var>. + * @return the matching AvdInfo or <code>null</code> if none were found. */ - public VmInfo getVm(String name) { - for (VmInfo info : mVmList) { + public AvdInfo getAvd(String name) { + for (AvdInfo info : mAvdList) { if (info.name.equals(name)) { return info; } @@ -105,19 +106,20 @@ public final class VmManager { } /** - * Creates a new VM. It is expected that there is no existing VM with this name already. - * @param parentFolder the folder to contain the VM. A new folder will be created in this - * folder with the name of the VM - * @param name the name of the VM - * @param target the target of the VM + * Creates a new AVD. It is expected that there is no existing AVD with this name already. + * @param parentFolder the folder to contain the AVD. A new folder will be created in this + * folder with the name of the AVD + * @param name the name of the AVD + * @param target the target of the AVD * @param skinName the name of the skin. Can be null. * @param sdcard the parameter value for the sdCard. Can be null. This is either a path to * an existing sdcard image or a sdcard size (\d+, \d+K, \dM). - * @param hardwareConfig the hardware setup for the VM + * @param hardwareConfig the hardware setup for the AVD + * @param removePrevious If true remove any previous files. */ - public VmInfo createVm(String parentFolder, String name, IAndroidTarget target, + public AvdInfo createAvd(String parentFolder, String name, IAndroidTarget target, String skinName, String sdcard, Map<String,String> hardwareConfig, - ISdkLog log) { + boolean removePrevious, ISdkLog log) { try { File rootDirectory = new File(parentFolder); @@ -128,24 +130,31 @@ public final class VmManager { return null; } - File vmFolder = new File(parentFolder, name + ".avm"); - if (vmFolder.exists()) { - if (log != null) { - log.error(null, "Folder %s is in the way.", vmFolder.getAbsolutePath()); + File avdFolder = new File(parentFolder, name + AVD_FOLDER_EXTENSION); + if (avdFolder.exists()) { + if (removePrevious) { + // AVD already exists and removePrevious is set, try to remove the + // directory's content first (but not the directory itself). + recursiveDelete(avdFolder); + } else { + // AVD shouldn't already exist if removePrevious is false. + if (log != null) { + log.error(null, "Folder %s is in the way.", avdFolder.getAbsolutePath()); + } + return null; } - return null; } - // create the vm folder. - vmFolder.mkdir(); + // create the AVD folder. + avdFolder.mkdir(); HashMap<String, String> values = new HashMap<String, String>(); // prepare the ini file. - String vmRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_VMS; - File iniFile = new File(vmRoot, name + ".ini"); - values.put(VM_INFO_PATH, vmFolder.getAbsolutePath()); - values.put(VM_INFO_TARGET, target.hashString()); + String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD; + File iniFile = new File(avdRoot, name + ".ini"); + values.put(AVD_INFO_PATH, avdFolder.getAbsolutePath()); + values.put(AVD_INFO_TARGET, target.hashString()); createConfigIni(iniFile, values); // writes the userdata.img in it. @@ -153,7 +162,7 @@ public final class VmManager { File userdataSrc = new File(imagePath, IMAGE_USERDATA); FileInputStream fis = new FileInputStream(userdataSrc); - File userdataDest = new File(vmFolder, IMAGE_USERDATA); + File userdataDest = new File(avdFolder, IMAGE_USERDATA); FileOutputStream fos = new FileOutputStream(userdataDest); byte[] buffer = new byte[4096]; @@ -193,7 +202,7 @@ public final class VmManager { Matcher m = SDCARD_SIZE_PATTERN.matcher(sdcard); if (m.matches()) { // create the sdcard. - sdcardFile = new File(vmFolder, "sdcard.img"); + sdcardFile = new File(avdFolder, "sdcard.img"); String path = sdcardFile.getAbsolutePath(); // execute mksdcard with the proper parameters. @@ -224,28 +233,27 @@ public final class VmManager { values.putAll(hardwareConfig); } - File configIniFile = new File(vmFolder, CONFIG_INI); + File configIniFile = new File(avdFolder, CONFIG_INI); createConfigIni(configIniFile, values); if (log != null) { if (target.isPlatform()) { - log.printf("Created VM '%s' based on %s\n", name, target.getName()); + log.printf("Created AVD '%s' based on %s\n", name, target.getName()); } else { - log.printf( - "Created VM '%s' based on %s (%s)\n", name, target.getName(), - target.getVendor()); + log.printf("Created AVD '%s' based on %s (%s)\n", name, target.getName(), + target.getVendor()); } } - // create the VmInfo object, and add it to the list - VmInfo vmInfo = new VmInfo(); - vmInfo.name = name; - vmInfo.path = vmFolder.getAbsolutePath(); - vmInfo.target = target; + // create the AvdInfo object, and add it to the list + AvdInfo avdInfo = new AvdInfo(); + avdInfo.name = name; + avdInfo.path = avdFolder.getAbsolutePath(); + avdInfo.target = target; - mVmList.add(vmInfo); + mAvdList.add(avdInfo); - return vmInfo; + return avdInfo; } catch (AndroidLocationException e) { if (log != null) { log.error(e, null); @@ -259,21 +267,35 @@ public final class VmManager { return null; } - private void buildVmList() throws AndroidLocationException { + /** + * Helper method to recursively delete a folder's content (but not the folder itself). + * + * @throws SecurityException like {@link File#delete()} does if file/folder is not writable. + */ + public void recursiveDelete(File folder) { + for (File f : folder.listFiles()) { + if (f.isDirectory()) { + recursiveDelete(folder); + } + f.delete(); + } + } + + private void buildAvdList() throws AndroidLocationException { // get the Android prefs location. - String vmRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_VMS; + String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD; // ensure folder validity. - File folder = new File(vmRoot); + File folder = new File(avdRoot); if (folder.isFile()) { - throw new AndroidLocationException(String.format("%s is not a valid folder.", vmRoot)); + throw new AndroidLocationException(String.format("%s is not a valid folder.", avdRoot)); } else if (folder.exists() == false) { // folder is not there, we create it and return folder.mkdirs(); return; } - File[] vms = folder.listFiles(new FilenameFilter() { + File[] avds = folder.listFiles(new FilenameFilter() { public boolean accept(File parent, String name) { if (INI_NAME_PATTERN.matcher(name).matches()) { // check it's a file and not a folder @@ -284,23 +306,23 @@ public final class VmManager { } }); - for (File vm : vms) { - VmInfo info = parseVmInfo(vm); + for (File avd : avds) { + AvdInfo info = parseAvdInfo(avd); if (info != null) { - mVmList.add(info); + mAvdList.add(info); } } } - private VmInfo parseVmInfo(File path) { + private AvdInfo parseAvdInfo(File path) { Map<String, String> map = SdkManager.parsePropertyFile(path, mSdkLog); - String vmPath = map.get(VM_INFO_PATH); - if (vmPath == null) { + String avdPath = map.get(AVD_INFO_PATH); + if (avdPath == null) { return null; } - String targetHash = map.get(VM_INFO_TARGET); + String targetHash = map.get(AVD_INFO_TARGET); if (targetHash == null) { return null; } @@ -310,14 +332,14 @@ public final class VmManager { return null; } - VmInfo info = new VmInfo(); + AvdInfo info = new AvdInfo(); Matcher matcher = INI_NAME_PATTERN.matcher(path.getName()); if (matcher.matches()) { info.name = matcher.group(1); } else { info.name = path.getName(); // really this should not happen. } - info.path = vmPath; + info.path = avdPath; info.target = target; return info; @@ -447,4 +469,14 @@ public final class VmManager { return process.waitFor(); } + /** + * Removes an {@link AvdInfo} from the internal list. + * + * @param avdInfo The {@link AvdInfo} to remove. + * @return true if this {@link AvdInfo} was present and has been removed. + */ + public boolean removeAvd(AvdInfo avdInfo) { + return mAvdList.remove(avdInfo); + } + } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/HardwareProperties.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/HardwareProperties.java index 98e97fe..ed5b949 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/vm/HardwareProperties.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/HardwareProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.sdklib.vm; +package com.android.sdklib.avd; import com.android.sdklib.ISdkLog; diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/VmSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java index dcc0b9e..4a0ee06 100644 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/VmSelector.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java @@ -17,7 +17,7 @@ package com.android.sdkuilib; import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.vm.VmManager.VmInfo; +import com.android.sdklib.avd.AvdManager.AvdInfo; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; @@ -40,16 +40,16 @@ import java.util.ArrayList; /** - * The VM selector is a table that is added to the given parent composite. + * The AVD selector is a table that is added to the given parent composite. * <p/> - * To use, create it using {@link #VmSelector(Composite, VmInfo[], boolean)} then - * call {@link #setSelection(VmInfo)}, {@link #setSelectionListener(SelectionListener)} + * To use, create it using {@link #AvdSelector(Composite, AvdInfo[], boolean)} then + * call {@link #setSelection(AvdInfo)}, {@link #setSelectionListener(SelectionListener)} * and finally use {@link #getFirstSelected()} or {@link #getAllSelected()} to retrieve the * selection. */ -public final class VmSelector { +public final class AvdSelector { - private VmInfo[] mVms; + private AvdInfo[] mAvds; private final boolean mAllowMultipleSelection; private SelectionListener mSelectionListener; private Table mTable; @@ -59,12 +59,12 @@ public final class VmSelector { * Creates a new SDK Target Selector. * * @param parent The parent composite where the selector will be added. - * @param vms The list of vms. This is <em>not</em> copied, the caller must not modify. + * @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify. * @param allowMultipleSelection True if more than one SDK target can be selected at the same * time. */ - public VmSelector(Composite parent, VmInfo[] vms, boolean allowMultipleSelection) { - mVms = vms; + public AvdSelector(Composite parent, AvdInfo[] avds, boolean allowMultipleSelection) { + mAvds = avds; // Layout has 1 column Composite group = new Composite(parent, SWT.NONE); @@ -89,7 +89,7 @@ public final class VmSelector { // create the table columns final TableColumn column0 = new TableColumn(mTable, SWT.NONE); - column0.setText("VM Name"); + column0.setText("AVD Name"); final TableColumn column1 = new TableColumn(mTable, SWT.NONE); column1.setText("Target Name"); final TableColumn column2 = new TableColumn(mTable, SWT.NONE); @@ -104,25 +104,25 @@ public final class VmSelector { } /** - * Sets a new set of VM, with an optional filter. + * Sets a new set of AVD, with an optional filter. * <p/>This must be called from the UI thread. * - * @param vms The list of vms. This is <em>not</em> copied, the caller must not modify. - * @param filter An IAndroidTarget. If non-null, only VM whose target are compatible with the + * @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify. + * @param filter An IAndroidTarget. If non-null, only AVD whose target are compatible with the * filter target will displayed an available for selection. */ - public void setVms(VmInfo[] vms, IAndroidTarget filter) { - mVms = vms; + public void setAvds(AvdInfo[] avds, IAndroidTarget filter) { + mAvds = avds; fillTable(mTable, filter); } /** - * Returns the list of known Vms. + * Returns the list of known AVDs. * <p/> * This is not a copy. Callers must <em>not</em> modify this array. */ - public VmInfo[] getVms() { - return mVms; + public AvdInfo[] getAvds() { + return mAvds; } /** @@ -151,11 +151,11 @@ public final class VmSelector { * @param target the target to be selection * @return true if the target could be selected, false otherwise. */ - public boolean setSelection(VmInfo target) { + public boolean setSelection(AvdInfo target) { boolean found = false; boolean modified = false; for (TableItem i : mTable.getItems()) { - if ((VmInfo) i.getData() == target) { + if ((AvdInfo) i.getData() == target) { found = true; if (!i.getChecked()) { modified = true; @@ -181,14 +181,14 @@ public final class VmSelector { * @see #getFirstSelected() * @return An array of selected items. The list can be empty but not null. */ - public VmInfo[] getAllSelected() { + public AvdInfo[] getAllSelected() { ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>(); for (TableItem i : mTable.getItems()) { if (i.getChecked()) { list.add((IAndroidTarget) i.getData()); } } - return list.toArray(new VmInfo[list.size()]); + return list.toArray(new AvdInfo[list.size()]); } /** @@ -198,10 +198,10 @@ public final class VmSelector { * @see #getAllSelected() * @return The first selected item or null. */ - public VmInfo getFirstSelected() { + public AvdInfo getFirstSelected() { for (TableItem i : mTable.getItems()) { if (i.getChecked()) { - return (VmInfo) i.getData(); + return (AvdInfo) i.getData(); } } return null; @@ -283,7 +283,7 @@ public final class VmSelector { } /** - * Fills the table with all VM. + * Fills the table with all AVD. * The table columns are: * <ul> * <li>column 0: sdk name @@ -294,14 +294,14 @@ public final class VmSelector { */ private void fillTable(final Table table, IAndroidTarget filter) { table.removeAll(); - if (mVms != null && mVms.length > 0) { + if (mAvds != null && mAvds.length > 0) { table.setEnabled(true); - for (VmInfo vm : mVms) { - if (filter == null || filter.isCompatibleBaseFor(vm.getTarget())) { + for (AvdInfo avd : mAvds) { + if (filter == null || filter.isCompatibleBaseFor(avd.getTarget())) { TableItem item = new TableItem(table, SWT.NONE); - item.setData(vm); - item.setText(0, vm.getName()); - IAndroidTarget target = vm.getTarget(); + item.setData(avd); + item.setText(0, avd.getName()); + IAndroidTarget target = avd.getTarget(); item.setText(1, target.getFullName()); item.setText(2, target.getApiVersionName()); item.setText(3, Integer.toString(target.getApiVersionNumber())); @@ -314,7 +314,7 @@ public final class VmSelector { TableItem item = new TableItem(table, SWT.NONE); item.setData(null); item.setText(0, "--"); - item.setText(1, "No VM available"); + item.setText(1, "No AVD available"); item.setText(2, "--"); item.setText(3, "--"); } @@ -365,13 +365,13 @@ public final class VmSelector { } /** - * Updates the description label with the path of the item's VM, if any. + * Updates the description label with the path of the item's AVD, if any. */ private void updateDescription(TableItem item) { if (item != null) { Object data = item.getData(); - if (data instanceof VmInfo) { - String newTooltip = ((VmInfo) data).getPath(); + if (data instanceof AvdInfo) { + String newTooltip = ((AvdInfo) data).getPath(); mDescription.setText(newTooltip == null ? "" : newTooltip); //$NON-NLS-1$ } } |