Merge "Remove singleton for multiDexLegacy marker" into ub-jack
diff --git a/dx/src/com/android/jack/dx/dex/file/DexFile.java b/dx/src/com/android/jack/dx/dex/file/DexFile.java
index 5bc9cb6..916d5c8 100644
--- a/dx/src/com/android/jack/dx/dex/file/DexFile.java
+++ b/dx/src/com/android/jack/dx/dex/file/DexFile.java
@@ -23,6 +23,7 @@
 import com.android.jack.dx.rop.cst.CstEnumRef;
 import com.android.jack.dx.rop.cst.CstFieldRef;
 import com.android.jack.dx.rop.cst.CstMethodRef;
+import com.android.jack.dx.rop.cst.CstPrototypeRef;
 import com.android.jack.dx.rop.cst.CstString;
 import com.android.jack.dx.rop.cst.CstType;
 import com.android.jack.dx.util.ByteArrayAnnotatedOutput;
@@ -442,6 +443,8 @@
       fieldIds.intern((CstFieldRef) cst);
     } else if (cst instanceof CstEnumRef) {
       fieldIds.intern(((CstEnumRef) cst).getFieldRef());
+    } else if (cst instanceof CstPrototypeRef) {
+      protoIds.intern(((CstPrototypeRef) cst).getPrototype());
     } else if (cst == null) {
       throw new NullPointerException("cst == null");
     }
@@ -467,6 +470,8 @@
       return methodIds.get(cst);
     } else if (cst instanceof CstFieldRef) {
       return fieldIds.get(cst);
+    } else if (cst instanceof CstPrototypeRef) {
+      return protoIds.get(cst);
     } else {
       return null;
     }
diff --git a/dx/src/com/android/jack/dx/dex/file/ProtoIdsSection.java b/dx/src/com/android/jack/dx/dex/file/ProtoIdsSection.java
index 33cb976..4740173 100644
--- a/dx/src/com/android/jack/dx/dex/file/ProtoIdsSection.java
+++ b/dx/src/com/android/jack/dx/dex/file/ProtoIdsSection.java
@@ -17,6 +17,7 @@
 package com.android.jack.dx.dex.file;
 
 import com.android.jack.dx.rop.cst.Constant;
+import com.android.jack.dx.rop.cst.CstPrototypeRef;
 import com.android.jack.dx.rop.type.Prototype;
 import com.android.jack.dx.util.AnnotatedOutput;
 import com.android.jack.dx.util.Hex;
@@ -24,6 +25,8 @@
 import java.util.Collection;
 import java.util.TreeMap;
 
+import javax.annotation.Nonnull;
+
 /**
  * Proto (method prototype) identifiers list section of a
  * {@code .dex} file.
@@ -53,8 +56,14 @@
 
   /** {@inheritDoc} */
   @Override
-  public IndexedItem get(Constant cst) {
-    throw new UnsupportedOperationException("unsupported");
+  @Nonnull
+  public IndexedItem get(@Nonnull Constant cst) {
+    throwIfNotPrepared();
+
+    IndexedItem result = protoIds.get(((CstPrototypeRef) cst).getPrototype());
+    assert result != null;
+
+    return result;
   }
 
   /**
diff --git a/dx/src/com/android/jack/dx/io/IndexType.java b/dx/src/com/android/jack/dx/io/IndexType.java
index 515af59..7129597 100644
--- a/dx/src/com/android/jack/dx/io/IndexType.java
+++ b/dx/src/com/android/jack/dx/io/IndexType.java
@@ -41,6 +41,9 @@
   /** field reference index */
   FIELD_REF,
 
+  /** prototype reference index */
+  PROTOTYPE_REF,
+
   /** inline method index (for inline linked method invocations) */
   INLINE_METHOD,
 
diff --git a/dx/src/com/android/jack/dx/rop/cst/CstPrototypeRef.java b/dx/src/com/android/jack/dx/rop/cst/CstPrototypeRef.java
new file mode 100644
index 0000000..c954aac
--- /dev/null
+++ b/dx/src/com/android/jack/dx/rop/cst/CstPrototypeRef.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 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.jack.dx.rop.cst;
+
+import com.android.jack.dx.rop.type.Prototype;
+import com.android.jack.dx.rop.type.Type;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Constants of type reference to a {@Link Prototype}.
+ */
+public final class CstPrototypeRef extends TypedConstant {
+
+  /** {@code non-null;} the reference prototype */
+  @Nonnull
+  private final Prototype prototype;
+
+  /**
+   * Constructs an instance from a {@link CstPrototypeRef}.
+   *
+   * @param prototype {@code non-null;} the referenced prototype
+   */
+  public CstPrototypeRef(@Nonnull Prototype prototype) {
+    assert prototype != null;
+    this.prototype = prototype;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public final boolean equals(@Nonnull Object other) {
+    if (!(other instanceof CstPrototypeRef)) {
+      return false;
+    }
+
+    return prototype.equals(((CstPrototypeRef) other).prototype);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public final int hashCode() {
+    return prototype.hashCode();
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected int compareTo0(@Nonnull Constant other) {
+    return prototype.compareTo(((CstPrototypeRef) other).prototype);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  @Nonnull
+  public String toString() {
+    return typeName() + '{' + toHuman() + '}';
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  @Nonnull
+  public String typeName() {
+    return "prototype";
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean isCategory2() {
+    return false;
+  }
+
+  /**
+   * Gets the referenced prototype. This doesn't include a {@code this} argument.
+   *
+   * @return {@code non-null;} the referenced prototype
+   */
+  @Nonnull
+  public final Prototype getPrototype() {
+    return prototype;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  @Nonnull
+  public Type getType() {
+    return prototype.getReturnType();
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  @Nonnull
+  public String toHuman() {
+    return prototype.toString();
+  }
+}
diff --git a/jack-api/src/com/android/jack/api/ConfigNotSupportedAnymoreException.java b/jack-api/src/com/android/jack/api/ConfigNotSupportedAnymoreException.java
new file mode 100644
index 0000000..8cc6c16
--- /dev/null
+++ b/jack-api/src/com/android/jack/api/ConfigNotSupportedAnymoreException.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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.jack.api;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Thrown when the requested Jack configuration for a given API version is not supported because
+ * this Jack does not support this configuration anymore.
+ */
+public class ConfigNotSupportedAnymoreException extends ConfigNotSupportedException {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * Construct the exception with no details.
+   */
+  public ConfigNotSupportedAnymoreException() {
+    super();
+  }
+
+  /**
+   * Construct the exception with a {@link String} message.
+   * @param message the message
+   */
+  public ConfigNotSupportedAnymoreException(@Nonnull String message) {
+    super(message);
+  }
+
+  /**
+   * Construct the exception with a {@link String} message and a {@link Throwable} cause.
+   * @param message the message
+   * @param cause the cause
+   */
+  public ConfigNotSupportedAnymoreException(@Nonnull String message, @Nonnull Throwable cause) {
+    super(message, cause);
+  }
+
+  /**
+   * Construct the exception with a {@link Throwable} cause.
+   * @param cause the cause
+   */
+  public ConfigNotSupportedAnymoreException(@Nonnull Throwable cause) {
+    super(cause);
+  }
+}
diff --git a/jack-api/src/com/android/jack/api/UnknownConfigException.java b/jack-api/src/com/android/jack/api/UnknownConfigException.java
new file mode 100644
index 0000000..1f6e028
--- /dev/null
+++ b/jack-api/src/com/android/jack/api/UnknownConfigException.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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.jack.api;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Thrown when the requested Jack configuration for a given API version is not supported because it
+ * is unknown to this Jack.
+ */
+public class UnknownConfigException extends ConfigNotSupportedException {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * Construct the exception with no details.
+   */
+  public UnknownConfigException() {
+    super();
+  }
+
+  /**
+   * Construct the exception with a {@link String} message.
+   * @param message the message
+   */
+  public UnknownConfigException(@Nonnull String message) {
+    super(message);
+  }
+
+  /**
+   * Construct the exception with a {@link String} message and a {@link Throwable} cause.
+   * @param message the message
+   * @param cause the cause
+   */
+  public UnknownConfigException(@Nonnull String message, @Nonnull Throwable cause) {
+    super(message, cause);
+  }
+
+  /**
+   * Construct the exception with a {@link Throwable} cause.
+   * @param cause the cause
+   */
+  public UnknownConfigException(@Nonnull Throwable cause) {
+    super(cause);
+  }
+}
diff --git a/jack-tests/prebuilts/core-stubs-mini.jack b/jack-tests/prebuilts/core-stubs-mini.jack
index 76a4383..f519179 100644
--- a/jack-tests/prebuilts/core-stubs-mini.jack
+++ b/jack-tests/prebuilts/core-stubs-mini.jack
Binary files differ
diff --git a/jack-tests/prebuilts/core-stubs-mini.jar b/jack-tests/prebuilts/core-stubs-mini.jar
index 114bfdc..99f6f63 100644
--- a/jack-tests/prebuilts/core-stubs-mini.jar
+++ b/jack-tests/prebuilts/core-stubs-mini.jar
Binary files differ
diff --git a/jack-tests/prebuilts/ecj-tests-lib.jar b/jack-tests/prebuilts/ecj-tests-lib.jar
index 49af442..e508f48 100644
--- a/jack-tests/prebuilts/ecj-tests-lib.jar
+++ b/jack-tests/prebuilts/ecj-tests-lib.jar
Binary files differ
diff --git a/jack-tests/prebuilts/junit4-lib.jack b/jack-tests/prebuilts/junit4-lib.jack
index 520459f..ad87d1f 100644
--- a/jack-tests/prebuilts/junit4-lib.jack
+++ b/jack-tests/prebuilts/junit4-lib.jack
Binary files differ
diff --git a/jack/prebuilts/core-stubs-mini.jack b/jack/prebuilts/core-stubs-mini.jack
index 76a4383..f519179 100644
--- a/jack/prebuilts/core-stubs-mini.jack
+++ b/jack/prebuilts/core-stubs-mini.jack
Binary files differ
diff --git a/jack/prebuilts/core-stubs-mini.jar b/jack/prebuilts/core-stubs-mini.jar
index 114bfdc..99f6f63 100644
--- a/jack/prebuilts/core-stubs-mini.jar
+++ b/jack/prebuilts/core-stubs-mini.jar
Binary files differ
diff --git a/jack/src/com/android/jack/api/impl/JackProviderImpl.java b/jack/src/com/android/jack/api/impl/JackProviderImpl.java
index fd08c4a..1192187 100644
--- a/jack/src/com/android/jack/api/impl/JackProviderImpl.java
+++ b/jack/src/com/android/jack/api/impl/JackProviderImpl.java
@@ -17,10 +17,12 @@
 package com.android.jack.api.impl;
 
 import com.android.jack.Jack;
+import com.android.jack.api.ConfigNotSupportedAnymoreException;
 import com.android.jack.api.ConfigNotSupportedException;
 import com.android.jack.api.JackConfig;
 import com.android.jack.api.JackProvider;
 import com.android.jack.api.ResourceController;
+import com.android.jack.api.UnknownConfigException;
 import com.android.jack.api.v01.Api01Config;
 import com.android.jack.api.v01.Cli01Config;
 import com.android.jack.api.v01.impl.Api01Feature;
@@ -74,9 +76,13 @@
   @Override
   @SuppressWarnings("unchecked")
   public <T extends JackConfig> T createConfig(Class<T> cls) throws ConfigNotSupportedException {
+    if (!impl.containsKey(cls)) {
+      throw new UnknownConfigException(cls.getName() + " is not supported");
+    }
+
     Class<? extends JackConfigImpl> clsImpl = impl.get(cls);
     if (clsImpl == null) {
-      throw new ConfigNotSupportedException(cls.getName() + " is not supported");
+      throw new ConfigNotSupportedAnymoreException(cls.getName() + " is not supported anymore");
     }
 
     try {
diff --git a/jack/src/com/android/jack/backend/dex/ClassDefItemBuilder.java b/jack/src/com/android/jack/backend/dex/ClassDefItemBuilder.java
index f91a606..ce3d7ba 100644
--- a/jack/src/com/android/jack/backend/dex/ClassDefItemBuilder.java
+++ b/jack/src/com/android/jack/backend/dex/ClassDefItemBuilder.java
@@ -142,14 +142,16 @@
     CstString sourceFile = null;
     SourceInfo sourceInfo = type.getSourceInfo();
 
-    // Only keep filename without the path
-    String sourceFileName = sourceInfo.getFileName();
-    String fileSeparator = FileUtils.getFileSeparator();
-    int separatorPos = sourceFileName.lastIndexOf(fileSeparator);
-    if (separatorPos > 0) {
-      sourceFileName = sourceFileName.substring(separatorPos + 1);
+    if (sourceInfo != SourceInfo.UNKNOWN) {
+      // Only keep filename without the path
+      String sourceFileName = sourceInfo.getFileName();
+      String fileSeparator = FileUtils.getFileSeparator();
+      int separatorPos = sourceFileName.lastIndexOf(fileSeparator);
+      if (separatorPos > 0) {
+        sourceFileName = sourceFileName.substring(separatorPos + 1);
+      }
+      sourceFile = new CstString(sourceFileName);
     }
-    sourceFile = new CstString(sourceFileName);
 
     return sourceFile;
   }
diff --git a/jack/src/com/android/jack/ecj/loader/jast/JAstBinaryMethod.java b/jack/src/com/android/jack/ecj/loader/jast/JAstBinaryMethod.java
index bb486dd..262318e 100644
--- a/jack/src/com/android/jack/ecj/loader/jast/JAstBinaryMethod.java
+++ b/jack/src/com/android/jack/ecj/loader/jast/JAstBinaryMethod.java
@@ -32,6 +32,7 @@
 import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
 import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
 import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+import org.eclipse.jdt.internal.compiler.lookup.TagBits;
 
 import java.util.List;
 
@@ -218,6 +219,11 @@
   @Override
   public long getTagBits() {
     long tagBits = AnnotationUtils.getTagBits(jMethod);
+
+    if (jMethod.hasPolymorphicSignature()) {
+      tagBits |= TagBits.AnnotationPolymorphicSignature;
+    }
+
     return tagBits;
   }
 
diff --git a/jack/src/com/android/jack/ir/ast/JMethod.java b/jack/src/com/android/jack/ir/ast/JMethod.java
index e131807..52769ce 100644
--- a/jack/src/com/android/jack/ir/ast/JMethod.java
+++ b/jack/src/com/android/jack/ir/ast/JMethod.java
@@ -18,11 +18,13 @@
 
 import com.android.jack.Jack;
 import com.android.jack.ir.JNodeInternalError;
+import com.android.jack.ir.formatter.UserFriendlyFormatter;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.jack.load.MethodLoader;
 import com.android.jack.load.NopMethodLoader;
 import com.android.jack.util.AnnotationUtils;
 import com.android.jack.util.NamingTools;
+import com.android.jack.util.TriStateBoolean;
 import com.android.sched.item.Component;
 import com.android.sched.item.Description;
 import com.android.sched.marker.Marker;
@@ -68,6 +70,9 @@
   @Nonnull
   private MethodLoader loader;
 
+  @Nonnull
+  TriStateBoolean hasPolymorphicSignature = TriStateBoolean.UNDEFINED;
+
   public JMethod(@Nonnull SourceInfo info,
       @Nonnull JMethodId methodId,
       @Nonnull JDefinedClassOrInterface enclosingType,
@@ -116,6 +121,7 @@
     assert JModifier.isMethodModifier(modifier);
     assert JModifier.isValidMethodModifier(modifier);
     this.modifier = modifier;
+    hasPolymorphicSignature = TriStateBoolean.UNDEFINED;
   }
 
   /**
@@ -123,6 +129,7 @@
    */
   public void addParam(JParameter parameter) {
     params.add(parameter);
+    hasPolymorphicSignature = TriStateBoolean.UNDEFINED;
   }
 
   /**
@@ -130,6 +137,7 @@
    */
   public void prependParam(JParameter parameter) {
     params.add(0, parameter);
+    hasPolymorphicSignature = TriStateBoolean.UNDEFINED;
   }
 
   /**
@@ -371,6 +379,8 @@
       if (!transform(annotations, existingNode, (JAnnotation) newNode, transformation)) {
         super.transform(existingNode, newNode, transformation);
       }
+    } else {
+      hasPolymorphicSignature = TriStateBoolean.UNDEFINED;
     }
   }
 
@@ -465,4 +475,21 @@
 
     return true;
   }
+
+  public boolean hasPolymorphicSignature() {
+    if (hasPolymorphicSignature.isUndefined()) {
+      UserFriendlyFormatter formatter = UserFriendlyFormatter.getFormatter();
+
+      if (formatter.getName(getEnclosingType()).equals("java.lang.invoke.MethodHandle")
+          && (isVarags() && params.size() == 1
+              && formatter.getName(params.get(0).getType()).equals("java.lang.Object[]"))
+          && formatter.getName(getType()).equals("java.lang.Object") && isNative()) {
+        hasPolymorphicSignature = TriStateBoolean.TRUE;
+      } else {
+        hasPolymorphicSignature = TriStateBoolean.FALSE;
+      }
+    }
+
+    return hasPolymorphicSignature.isTrue();
+  }
 }
diff --git a/jack/src/com/android/jack/ir/impl/JackIrBuilder.java b/jack/src/com/android/jack/ir/impl/JackIrBuilder.java
index 414c48c..609f606 100644
--- a/jack/src/com/android/jack/ir/impl/JackIrBuilder.java
+++ b/jack/src/com/android/jack/ir/impl/JackIrBuilder.java
@@ -2868,7 +2868,7 @@
         ((JMethodBody) body).getBlock().addStmt(
             new JReturnStatement(session.getSourceInfoFactory().create(
                 method.getSourceInfo().getEndLine(), method.getSourceInfo().getEndLine(),
-                method.getSourceInfo().getFileName()), null));
+                method.getSourceInfo()), null));
 
         addAnnotations(x.annotations, curClass.type);
 
@@ -3842,7 +3842,7 @@
       curMethod.body.getBlock().addStmt(new JReturnStatement(session.getSourceInfoFactory().create(
           curMethod.method.getSourceInfo().getEndLine(),
           curMethod.method.getSourceInfo().getEndLine(),
-          curMethod.method.getSourceInfo().getFileName()), null));
+          curMethod.method.getSourceInfo()), null));
     }
   }
 
diff --git a/jack/src/com/android/jack/ir/sourceinfo/SourceInfo.java b/jack/src/com/android/jack/ir/sourceinfo/SourceInfo.java
index cee4c88..3dd22e9 100644
--- a/jack/src/com/android/jack/ir/sourceinfo/SourceInfo.java
+++ b/jack/src/com/android/jack/ir/sourceinfo/SourceInfo.java
@@ -19,6 +19,7 @@
 import com.android.sched.util.location.FileLocation;
 import com.android.sched.util.location.HasLocation;
 import com.android.sched.util.location.Location;
+import com.android.sched.util.location.NoLocation;
 
 import javax.annotation.Nonnull;
 
@@ -61,28 +62,30 @@
   @Override
   @Nonnull
   public Location getLocation() {
-    Location location;
-    FileLocation fileLocation = new FileLocation(getFileName());
-    if (getStartLine() != UNKNOWN_LINE_NUMBER) {
-      int endLine = getEndLine();
-      if (endLine == UNKNOWN_LINE_NUMBER) {
-        endLine = ColumnAndLineLocation.UNKNOWN;
-      }
+    Location location = NoLocation.getInstance();
+    if (this != SourceInfo.UNKNOWN) {
+      FileLocation fileLocation = new FileLocation(getFileName());
+      if (getStartLine() != UNKNOWN_LINE_NUMBER) {
+        int endLine = getEndLine();
+        if (endLine == UNKNOWN_LINE_NUMBER) {
+          endLine = ColumnAndLineLocation.UNKNOWN;
+        }
 
-      int startColumn = getStartColumn();
-      if (startColumn == UNKNOWN_LINE_NUMBER) {
-        startColumn = ColumnAndLineLocation.UNKNOWN;
-      }
+        int startColumn = getStartColumn();
+        if (startColumn == UNKNOWN_LINE_NUMBER) {
+          startColumn = ColumnAndLineLocation.UNKNOWN;
+        }
 
-      int endColumn = getEndColumn();
-      if (endColumn == UNKNOWN_LINE_NUMBER) {
-        endColumn = ColumnAndLineLocation.UNKNOWN;
-      }
+        int endColumn = getEndColumn();
+        if (endColumn == UNKNOWN_LINE_NUMBER) {
+          endColumn = ColumnAndLineLocation.UNKNOWN;
+        }
 
-      location =
-          new ColumnAndLineLocation(fileLocation, getStartLine(), endLine, startColumn, endColumn);
-    } else {
-      location = fileLocation;
+        location = new ColumnAndLineLocation(fileLocation, getStartLine(), endLine, startColumn,
+            endColumn);
+      } else {
+        location = fileLocation;
+      }
     }
     return location;
   }
diff --git a/jack/src/com/android/jack/ir/sourceinfo/SourceInfoFactory.java b/jack/src/com/android/jack/ir/sourceinfo/SourceInfoFactory.java
index bff5df1..66b2042 100644
--- a/jack/src/com/android/jack/ir/sourceinfo/SourceInfoFactory.java
+++ b/jack/src/com/android/jack/ir/sourceinfo/SourceInfoFactory.java
@@ -49,6 +49,7 @@
    */
   @Nonnull
   public FileSourceInfo create(@Nonnull String fileName) {
+    assert fileName != null;
     FileSourceInfo newInstance = canonicalFileSourceInfos.get(fileName);
     if (newInstance == null) {
       newInstance = new FileSourceInfo(fileName);
@@ -108,13 +109,19 @@
   }
 
   /**
-   * Creates SourceInfo nodes. This factory method will provide
-   * canonicalized instances of SourceInfo objects.
+   * Creates SourceInfo nodes with a file name coming from another SourceInfo. This factory method
+   * will provide canonicalized instances of SourceInfo objects.
    */
   @Nonnull
-  public LineSourceInfo create(
-      @Nonnegative int startLine, @Nonnegative int endLine, @Nonnull FileSourceInfo fileName) {
-    LineSourceInfo newInstance = new LineSourceInfo(fileName, startLine, endLine);
+  public SourceInfo create(@Nonnegative int startLine, @Nonnegative int endLine,
+      @Nonnull SourceInfo originalSourceInfo) {
+    if (originalSourceInfo == SourceInfo.UNKNOWN) {
+      assert startLine == SourceInfo.UNKNOWN_LINE_NUMBER;
+      assert endLine == SourceInfo.UNKNOWN_LINE_NUMBER;
+      return SourceInfo.UNKNOWN;
+    }
+    LineSourceInfo newInstance =
+        new LineSourceInfo(originalSourceInfo.getFileSourceInfo(), startLine, endLine);
     LineSourceInfo canonical = canonicalLineSourceInfos.get(newInstance);
 
     assert canonical == null || (newInstance != canonical && newInstance.equals(canonical));
diff --git a/jack/src/com/android/jack/ir/sourceinfo/UnknownSourceInfo.java b/jack/src/com/android/jack/ir/sourceinfo/UnknownSourceInfo.java
index 05c5f3c..c662a1a 100644
--- a/jack/src/com/android/jack/ir/sourceinfo/UnknownSourceInfo.java
+++ b/jack/src/com/android/jack/ir/sourceinfo/UnknownSourceInfo.java
@@ -22,10 +22,9 @@
 /**
  * Represents the source information when the origin of a node is unknown.
  */
-public class UnknownSourceInfo extends FileSourceInfo {
+public class UnknownSourceInfo extends SourceInfo {
 
   UnknownSourceInfo() {
-    super("Unknown");
   }
 
   @Override
@@ -33,4 +32,10 @@
   public String toString() {
     return "Unknown source info";
   }
+
+  @Override
+  @Nonnull
+  public FileSourceInfo getFileSourceInfo() {
+    throw new UnsupportedOperationException();
+  }
 }
diff --git a/jack/src/com/android/jack/jayce/v0004/Version.java b/jack/src/com/android/jack/jayce/v0004/Version.java
index 218179e..76296d3 100644
--- a/jack/src/com/android/jack/jayce/v0004/Version.java
+++ b/jack/src/com/android/jack/jayce/v0004/Version.java
@@ -21,7 +21,7 @@
  */
 public class Version {
 
-  public static final int MINOR_MIN = 0;
+  public static final int MINOR_MIN = 1;
 
-  public static final int CURRENT_MINOR = 0;
+  public static final int CURRENT_MINOR = 1;
 }
diff --git a/jack/src/com/android/jack/jayce/v0004/io/JayceInternalReaderImpl.java b/jack/src/com/android/jack/jayce/v0004/io/JayceInternalReaderImpl.java
index 10c4626..2f32759 100644
--- a/jack/src/com/android/jack/jayce/v0004/io/JayceInternalReaderImpl.java
+++ b/jack/src/com/android/jack/jayce/v0004/io/JayceInternalReaderImpl.java
@@ -109,6 +109,11 @@
   public String readCurrentFileName() throws IOException {
     if (tokenizer.readOpenFileName()) {
       currentFileName = readString();
+      // UNKNOW_LINE_NUMBER is not dump for unknown debug information, reset it automatically.
+      // Current file name sets to null means unknown debug information.
+      if (currentFileName == null) {
+        currentLine = SourceInfo.UNKNOWN_LINE_NUMBER;
+      }
       tokenizer.readCloseFileName();
     }
     return currentFileName;
@@ -206,8 +211,6 @@
       return null;
     }
 
-
-
     tokenizer.readOpen();
     NNode node;
     try {
@@ -238,8 +241,10 @@
     if (nodeLevel != NodeLevel.TYPES) {
 
       if (node instanceof HasSourceInfo) {
+        fileName = readCurrentFileName();
         int endLine = readCurrentLine();
-        if (fileName == null && startLine == 0 && endLine == 0) {
+        if (fileName == null) {
+          assert startLine == 0 && endLine == 0;
           ((HasSourceInfo) node).setSourceInfos(SourceInfo.UNKNOWN);
         } else {
           assert fileName != null;
diff --git a/jack/src/com/android/jack/jayce/v0004/io/JayceInternalWriterImpl.java b/jack/src/com/android/jack/jayce/v0004/io/JayceInternalWriterImpl.java
index 79d52d7..645e246 100644
--- a/jack/src/com/android/jack/jayce/v0004/io/JayceInternalWriterImpl.java
+++ b/jack/src/com/android/jack/jayce/v0004/io/JayceInternalWriterImpl.java
@@ -92,15 +92,24 @@
   private void writeSourceInfoBegin(@Nonnull NNode node) throws IOException {
     if (node instanceof HasSourceInfo) {
       SourceInfo sourceInfo = ((HasSourceInfo) node).getSourceInfos();
-      writeFileNameIfDifferentFromCurrent(sourceInfo.getFileName());
-      writeLineIfDifferentFromCurrent(sourceInfo.getStartLine());
+      if (sourceInfo == SourceInfo.UNKNOWN) {
+        writeUnknowDebug();
+      } else {
+        writeFileNameIfDifferentFromCurrent(sourceInfo.getFileName());
+        writeLineIfDifferentFromCurrent(sourceInfo.getStartLine());
+      }
     }
   }
 
   private void writeSourceInfoEnd(@Nonnull NNode node) throws IOException {
     if (node instanceof HasSourceInfo) {
-      writeLineIfDifferentFromCurrent(
-          ((HasSourceInfo) node).getSourceInfos().getEndLine());
+      SourceInfo sourceInfo = ((HasSourceInfo) node).getSourceInfos();
+      if (sourceInfo == SourceInfo.UNKNOWN) {
+        writeUnknowDebug();
+      } else {
+        writeFileNameIfDifferentFromCurrent(sourceInfo.getFileName());
+        writeLineIfDifferentFromCurrent(sourceInfo.getEndLine());
+      }
     }
   }
 
@@ -258,22 +267,30 @@
     }
   }
 
-  public void writeCurrentFileName(@Nonnull String fileName)  throws IOException {
+  private void writeUnknowDebug()  throws IOException {
+    if (currentFileName != null) {
+      writeOpenFileName();
+      writeString(null);
+      writeCloseFileName();
+      currentFileName = null;
+      currentLineNumber = 0;
+    }
+  }
+
+  private void writeCurrentFileName(@CheckForNull String fileName)  throws IOException {
     writeOpenFileName();
     writeString(fileName);
     writeCloseFileName();
     currentFileName = fileName;
   }
 
-  public void writeLineIfDifferentFromCurrent(@Nonnegative int lineNumber)
-      throws IOException {
+  public void writeLineIfDifferentFromCurrent(@Nonnegative int lineNumber) throws IOException {
     if (lineNumber != currentLineNumber) {
       writeCurrentLine(lineNumber);
     }
   }
 
-  public void writeCurrentLine(@Nonnegative int lineNumber)
-      throws IOException {
+  public void writeCurrentLine(@Nonnegative int lineNumber) throws IOException {
     writeOpenLineInfo();
     writeTrimmedInt(lineNumber);
     writeCloseLineInfo();
diff --git a/jack/src/com/android/jack/library/v0003/Version.java b/jack/src/com/android/jack/library/v0003/Version.java
index bf25c2a1..d4d843d 100644
--- a/jack/src/com/android/jack/library/v0003/Version.java
+++ b/jack/src/com/android/jack/library/v0003/Version.java
@@ -21,9 +21,9 @@
  */
 public class Version {
 
-  public static final int MINOR_MIN = 1;
+  public static final int MINOR_MIN = 2;
 
-  public static final int MINOR = 1;
+  public static final int MINOR = 2;
 
   public static final int MAJOR = 3;
 }
diff --git a/jack/src/com/android/jack/shrob/shrink/KeepMarker.java b/jack/src/com/android/jack/shrob/shrink/KeepMarker.java
index 960dfab..1c6a193 100644
--- a/jack/src/com/android/jack/shrob/shrink/KeepMarker.java
+++ b/jack/src/com/android/jack/shrob/shrink/KeepMarker.java
@@ -31,12 +31,6 @@
 @Description("Indicates that this class or member should not be removed when shrinking.")
 public class KeepMarker extends BaseTracerMarker {
 
-  @Nonnull
-  public static final KeepMarker INSTANCE = new KeepMarker();
-
-  private KeepMarker() {
-  }
-
   @DynamicValidOn
   public boolean isValidOn(@Nonnull JDefinedClassOrInterface type) {
     return type.isToEmit();
diff --git a/jack/src/com/android/jack/shrob/shrink/KeeperBrush.java b/jack/src/com/android/jack/shrob/shrink/KeeperBrush.java
index 8915f0c..84890ee 100644
--- a/jack/src/com/android/jack/shrob/shrink/KeeperBrush.java
+++ b/jack/src/com/android/jack/shrob/shrink/KeeperBrush.java
@@ -79,7 +79,7 @@
   @Nonnull
   @Override
   protected KeepMarker createMarkerFor(@Nonnull JNode node) {
-    return KeepMarker.INSTANCE;
+    return new KeepMarker();
   }
 
   @Override
diff --git a/jack/src/com/android/jack/transformations/ast/TryWithResourcesTransformer.java b/jack/src/com/android/jack/transformations/ast/TryWithResourcesTransformer.java
index 7bccea2..4ac6d7c 100644
--- a/jack/src/com/android/jack/transformations/ast/TryWithResourcesTransformer.java
+++ b/jack/src/com/android/jack/transformations/ast/TryWithResourcesTransformer.java
@@ -194,13 +194,11 @@
       if (x.getResourcesDeclarations().size() > 0) {
 
         SourceInfo trySourceInfo = x.getSourceInfo();
-        SourceInfo endOfTrySourceInfos = sourceInfoFactory.create(
-            trySourceInfo.getEndLine(), trySourceInfo.getEndLine(),
-            trySourceInfo.getFileSourceInfo());
+        SourceInfo endOfTrySourceInfos = sourceInfoFactory.create(trySourceInfo.getEndLine(),
+            trySourceInfo.getEndLine(), trySourceInfo);
 
-        SourceInfo firstLineSourceInfos = sourceInfoFactory.create(
-            trySourceInfo.getStartLine(), trySourceInfo.getStartLine(),
-            trySourceInfo.getFileSourceInfo());
+        SourceInfo firstLineSourceInfos = sourceInfoFactory.create(trySourceInfo.getStartLine(),
+            trySourceInfo.getStartLine(), trySourceInfo);
 
         JBlock finalTryBlock = new JBlock(trySourceInfo);
 
diff --git a/jack/src/com/android/jack/util/TriStateBoolean.java b/jack/src/com/android/jack/util/TriStateBoolean.java
new file mode 100644
index 0000000..8e41166
--- /dev/null
+++ b/jack/src/com/android/jack/util/TriStateBoolean.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 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.jack.util;
+
+/**
+ * Boolean with three state.
+ */
+public enum TriStateBoolean {
+  UNDEFINED,
+  TRUE,
+  FALSE;
+
+  public boolean isTrue() {
+    return this == TRUE;
+  }
+
+  public boolean isFalse() {
+    return this == FALSE;
+  }
+
+  public boolean isUndefined() {
+    return this == UNDEFINED;
+  }
+}
diff --git a/jill/src/com/android/jill/frontend/java/JavaTransformer.java b/jill/src/com/android/jill/frontend/java/JavaTransformer.java
index b7d0cc8..27402a9 100644
--- a/jill/src/com/android/jill/frontend/java/JavaTransformer.java
+++ b/jill/src/com/android/jill/frontend/java/JavaTransformer.java
@@ -77,13 +77,13 @@
   private static final String LIB_MAJOR_VERSION = "3";
 
   @Nonnull
-  private static final String LIB_MINOR_VERSION = "1";
+  private static final String LIB_MINOR_VERSION = "2";
 
   @Nonnull
   private static final String JAYCE_MAJOR_VERSION = "4";
 
   @Nonnull
-  private static final String JAYCE_MINOR_VERSION = "0";
+  private static final String JAYCE_MINOR_VERSION = "1";
 
   @Nonnull
   private static final String KEY_LIB_MAJOR_VERSION = "lib.version.major";
diff --git a/jill/src/com/android/jill/frontend/java/SourceInfoWriter.java b/jill/src/com/android/jill/frontend/java/SourceInfoWriter.java
index 9ef4f22..19b81d6 100644
--- a/jill/src/com/android/jill/frontend/java/SourceInfoWriter.java
+++ b/jill/src/com/android/jill/frontend/java/SourceInfoWriter.java
@@ -55,33 +55,26 @@
     writeDebugBegin(cn, NO_LINE);
   }
 
-  public void writeDebugBegin(@Nonnull ClassNode cn, @Nonnull FieldNode fn)
-      throws IOException {
+  public void writeDebugBegin(@Nonnull ClassNode cn, @Nonnull FieldNode fn) throws IOException {
     writeUnknwonDebugBegin();
   }
 
-  public void writeDebugBegin(@Nonnull ClassNode cn, int startLine)
-      throws IOException {
+  public void writeDebugBegin(@Nonnull ClassNode cn, int startLine) throws IOException {
     if (cn.sourceFile == null) {
       writeUnknwonDebugBegin();
     } else {
-      writeDebugBeginInternal(cn.sourceFile, startLine);
+      writeFileNameIfDifferentFromCurrent(cn.sourceFile);
+      writeLineIfDifferentFromCurrent(startLine);
     }
   }
 
   public void writeUnknwonDebugBegin() throws IOException {
-    writeDebugBeginInternal(NO_FILENAME, NO_LINE);
-  }
-
-  private void writeDebugBeginInternal(@CheckForNull String sourceFile, int startLine)
-      throws IOException {
-    writeFileNameIfDifferentFromCurrent(sourceFile);
-    writeLineIfDifferentFromCurrent(startLine);
+    writeUnknowDebug();
   }
 
   public void writeDebugEnd(@Nonnull ClassNode cn)
       throws IOException {
-    writeUnknownDebugEnd();
+    writeDebugEnd(cn, NO_LINE);
   }
 
   public void writeDebugEnd(@Nonnull ClassNode cn, @Nonnull FieldNode fn)
@@ -91,32 +84,39 @@
 
   public void writeDebugEnd(@Nonnull ClassNode cn, int endLine) throws IOException {
     if (cn.sourceFile == null) {
-      writeUnknownDebugEnd();
+      writeUnknwonDebugBegin();
     } else {
+      writeFileNameIfDifferentFromCurrent(cn.sourceFile);
       writeLineIfDifferentFromCurrent(endLine);
     }
   }
 
   public void writeUnknownDebugEnd() throws IOException {
-    writeLineIfDifferentFromCurrent(NO_LINE);
+    writeUnknowDebug();
   }
 
-  private void writeFileNameIfDifferentFromCurrent(@CheckForNull String fileName)
+  private void writeUnknowDebug()  throws IOException {
+    if (currentFileName != null) {
+      writeCurrentFileName(null);
+      currentLineNumber = 0;
+    }
+  }
+
+  private void writeFileNameIfDifferentFromCurrent(@Nonnull String fileName)
       throws IOException {
-    if (fileName != null && !fileName.equals(currentFileName)) {
+    if (!fileName.equals(currentFileName)) {
       writeCurrentFileName(fileName);
     }
-    // Assume that elements with unknown debug infos are in same file.
   }
 
-  private void writeCurrentFileName(@Nonnull String fileName)
+  private void writeCurrentFileName(@CheckForNull String fileName)
       throws IOException {
     writer.writeFileName(fileName);
     currentFileName = fileName;
   }
 
-  private void writeLineIfDifferentFromCurrent(@Nonnegative int lineNumber)
-      throws IOException {
+  private void writeLineIfDifferentFromCurrent(@Nonnegative int lineNumber) throws IOException {
+    assert currentFileName != NO_FILENAME || lineNumber == NO_LINE;
     if (lineNumber != currentLineNumber) {
       writeCurrentLine(lineNumber);
     }
diff --git a/sched/src/com/android/sched/util/RunnableHooks.java b/sched/src/com/android/sched/util/RunnableHooks.java
index 07430fc..33a5cdb 100644
--- a/sched/src/com/android/sched/util/RunnableHooks.java
+++ b/sched/src/com/android/sched/util/RunnableHooks.java
@@ -21,9 +21,12 @@
 import com.android.sched.util.config.HasKeyId;
 import com.android.sched.util.config.ThreadConfig;
 import com.android.sched.util.config.id.ObjectId;
+import com.android.sched.util.log.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import javax.annotation.Nonnull;
 
@@ -37,6 +40,8 @@
 @HasKeyId
 public class RunnableHooks {
   @Nonnull
+  private final Logger logger = LoggerFactory.getLogger();
+  @Nonnull
   private static final ObjectId<RunnableHooks> SHUTDOWN_HOOKS =
       new ObjectId<RunnableHooks>("sched.internal.shutdown", RunnableHooks.class);
 
@@ -56,11 +61,30 @@
   }
 
   public synchronized void runHooks() {
+    Throwable current = null;
+
     for (Runnable hook : Lists.reverse(hooks)) {
-      hook.run();
+      try {
+        hook.run();
+      } catch (Error | RuntimeException e) {
+        logger.log(Level.SEVERE, "Uncaught exception during RunnableHook", e);
+        if (current == null) {
+          // Throw only the first one
+          current = e;
+        }
+      }
     }
 
     hooks.clear();
+    if (current != null) {
+      if (current instanceof Error) {
+        throw (Error) current;
+      } else if (current instanceof RuntimeException)  {
+        throw (RuntimeException) current;
+      } else {
+        throw new AssertionError(current);
+      }
+    }
   }
 
   /**
diff --git a/server/jack-server/src/com/android/jack/server/JackHttpServer.java b/server/jack-server/src/com/android/jack/server/JackHttpServer.java
index 2112a62..3534a4c 100644
--- a/server/jack-server/src/com/android/jack/server/JackHttpServer.java
+++ b/server/jack-server/src/com/android/jack/server/JackHttpServer.java
@@ -147,6 +147,9 @@
  */
 public class JackHttpServer implements HasVersion {
 
+  @Nonnull
+  public static final String VERSION_CODE = "jack-server";
+
   /**
    * Define an assertion status.
    */
@@ -502,7 +505,7 @@
   @Nonnull
   public static Version getServerVersion() {
     try {
-      return new Version("jack-server", JackHttpServer.class.getClassLoader());
+      return new Version(VERSION_CODE, JackHttpServer.class.getClassLoader());
     } catch (IOException e) {
       logger.log(Level.SEVERE, "Failed to read Jack-server version properties", e);
       throw new AssertionError();
@@ -869,9 +872,6 @@
       NoSuchAlgorithmException {
     {
       File clientPEM = new File(getServerDir(), PEM_CLIENT);
-      if (clientPEM.exists() && !clientPEM.delete()) {
-        throw new IOException("Failed to delete '" + clientPEM.getPath() + "'");
-      }
 
       PEMWriter pem = new PEMWriter(clientPEM);
       try {
@@ -883,9 +883,6 @@
     }
     {
       File serverPEM = new File(getServerDir(), PEM_SERVER);
-      if (serverPEM.exists() && !serverPEM.delete()) {
-        throw new IOException("Failed to delete '" + serverPEM.getPath() + "'");
-      }
       PEMWriter pem = new PEMWriter(serverPEM);
       try {
         pem.writeCertificate(keystoreServer.getCertificate(SERVER_KEY_ALIAS));
@@ -1120,7 +1117,7 @@
     }
   }
 
-  public long startingServiceTask() throws ServerClosedException {
+  public long startingServiceTask() {
     return startingTask(serviceInfo);
   }
 
@@ -1129,20 +1126,23 @@
   }
 
   public long startingAdministrativeTask() throws ServerClosedException {
-    return startingTask(adminInfo);
+    synchronized (lock) {
+      if (shuttingDown) {
+        throw new ServerClosedException();
+      }
+
+      return startingTask(adminInfo);
+    }
   }
 
   public void endingAdministrativeTask() {
     endingTask(adminInfo);
   }
 
-  private long startingTask(@Nonnull ServerInfo info) throws ServerClosedException{
+  private long startingTask(@Nonnull ServerInfo info) {
     long id;
 
     synchronized (lock) {
-      if (shuttingDown) {
-        throw new ServerClosedException();
-      }
       id = info.totalLocal;
       info.totalLocal++;
       setServerMode(ServerMode.WORK);
diff --git a/server/jack-server/src/com/android/jack/server/PEMWriter.java b/server/jack-server/src/com/android/jack/server/PEMWriter.java
index d770ffb..ecab1cf 100644
--- a/server/jack-server/src/com/android/jack/server/PEMWriter.java
+++ b/server/jack-server/src/com/android/jack/server/PEMWriter.java
@@ -54,21 +54,30 @@
   @Nonnull
   private final Writer out;
 
+  @Nonnull
+  private final File targetFile;
+
+
+  @Nonnull
+  private final File tmpFile;
+
   public PEMWriter(@Nonnull File file) throws IOException {
-    if (!file.exists()) {
-      if (!file.createNewFile()) {
-        throw new IOException("Failed to create file '" + file.getPath() + "'");
+    targetFile = file;
+    tmpFile = File.createTempFile("jackserver-", file.getName(), file.getParentFile());
+    if (!tmpFile.exists()) {
+      if (!tmpFile.createNewFile()) {
+        throw new IOException("Failed to create temp file '" + tmpFile.getPath() + "'");
       }
-      if  (!(file.setExecutable(false, false)
-          && file.setWritable(false, false)
-          && file.setReadable(false, false)
-          && file.setWritable(true, true)
-          && file.setReadable(true, true))) {
-        throw new IOException("Failed to set permission of '" + file.getPath() + "'");
+      if  (!(tmpFile.setExecutable(false, false)
+          && tmpFile.setWritable(false, false)
+          && tmpFile.setReadable(false, false)
+          && tmpFile.setWritable(true, true)
+          && tmpFile.setReadable(true, true))) {
+        throw new IOException("Failed to set permission of '" + tmpFile.getPath() + "'");
       }
     }
 
-    out = new OutputStreamWriter(new FileOutputStream(file), CHARSET);
+    out = new OutputStreamWriter(new FileOutputStream(tmpFile), CHARSET);
   }
 
   public void writeCertificate(@Nonnull Certificate certificate) throws IOException {
@@ -96,5 +105,9 @@
   @Override
   public void close() throws IOException {
     out.close();
+    if (!tmpFile.renameTo(targetFile)) {
+      throw new IOException("Failed to rename \"" + tmpFile.getPath() + "\" to \""
+          + targetFile.getPath() + "\"");
+    }
   }
 }
diff --git a/server/jack-server/src/com/android/jack/server/tasks/InstallServer.java b/server/jack-server/src/com/android/jack/server/tasks/InstallServer.java
index a326952..0aabf11 100644
--- a/server/jack-server/src/com/android/jack/server/tasks/InstallServer.java
+++ b/server/jack-server/src/com/android/jack/server/tasks/InstallServer.java
@@ -19,16 +19,31 @@
 import com.android.jack.server.JackHttpServer;
 import com.android.jack.server.api.v01.NotInstalledException;
 import com.android.jack.server.api.v01.ServerException;
+import com.android.sched.util.UncomparableVersion;
+import com.android.sched.util.Version;
+import com.android.sched.util.file.CannotChangePermissionException;
+import com.android.sched.util.file.CannotCreateFileException;
+import com.android.sched.util.file.CannotReadException;
+import com.android.sched.util.file.CannotWriteException;
+import com.android.sched.util.file.Files;
+import com.android.sched.util.location.FileLocation;
+import com.android.sched.util.location.StringLocation;
+import com.android.sched.util.stream.LocationByteStreamSucker;
 
 import org.simpleframework.http.Part;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 import org.simpleframework.http.Status;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 
 import javax.annotation.Nonnull;
 
@@ -38,6 +53,9 @@
 public class InstallServer extends SynchronousAdministrativeTask {
 
   @Nonnull
+  private static final String VERSION_FILE_SUFFIX = "-version.properties";
+
+  @Nonnull
   private static final Logger logger = Logger.getLogger(InstallServer.class.getName());
   public InstallServer(@Nonnull JackHttpServer jackServer) {
     super(jackServer);
@@ -55,6 +73,50 @@
     InputStream jarIn = null;
     try {
       jarIn = jarPart.getInputStream();
+      if (!force) {
+        File tmpServer = Files.createTempFile("jackserver-");
+        try (FileOutputStream out = new FileOutputStream(tmpServer)) {
+          new LocationByteStreamSucker(jarIn, out, new StringLocation("Request input"),
+              new FileLocation(tmpServer)).suck();
+        }
+
+        try (ZipFile zip = new ZipFile(tmpServer)) {
+          ZipEntry entry = zip.getEntry(JackHttpServer.VERSION_CODE + VERSION_FILE_SUFFIX);
+          if (entry == null) {
+            throw new IOException("Given server jar is invalid, it is missing a version file");
+          }
+          try (InputStream versionInput = zip.getInputStream(entry);) {
+            Version candidateVersion = new Version(versionInput);
+            Version currentVerion = jackServer.getVersion();
+
+            try {
+              if (!candidateVersion.isNewerThan(currentVerion)) {
+                if (candidateVersion.equals(currentVerion)) {
+                  logger.log(Level.INFO, "Server version "
+                      + currentVerion.getVerboseVersion() + " was already installed");
+                  return;
+                } else {
+                  throw new NotInstalledException("Not installing server "
+                      + candidateVersion.getVerboseVersion()
+                      + " since it is not newer than current server "
+                      + currentVerion.getVerboseVersion());
+                }
+              }
+            } catch (UncomparableVersion e) {
+              if (!candidateVersion.isComparable()) {
+                throw new NotInstalledException("Not installing server '"
+                    + candidateVersion.getVerboseVersion() + "' without force request");
+              }
+              // else: current is experimental or eng, candidate is not, lets proceed
+            }
+          }
+        }
+
+        // replace jarIn, the server jar is no longer available from the request
+        jarIn.close();
+        jarIn = new FileInputStream(tmpServer);
+      }
+
       jackServer.shutdownServerOnly();
       jackServer.getLauncherHandle().replaceServer(
           jarIn,
@@ -64,7 +126,8 @@
       logger.log(Level.SEVERE, e.getMessage(), e);
       response.setStatus(Status.INTERNAL_SERVER_ERROR);
       return;
-    } catch (IOException e) {
+    } catch (CannotChangePermissionException | CannotReadException | CannotCreateFileException
+           | CannotWriteException | IOException e) {
       logger.log(Level.SEVERE, e.getMessage(), e);
       response.setStatus(Status.INTERNAL_SERVER_ERROR);
       return;
diff --git a/server/jack-server/src/com/android/jack/server/tasks/SynchronousServiceTask.java b/server/jack-server/src/com/android/jack/server/tasks/SynchronousServiceTask.java
index 914bf16..7d943a1 100644
--- a/server/jack-server/src/com/android/jack/server/tasks/SynchronousServiceTask.java
+++ b/server/jack-server/src/com/android/jack/server/tasks/SynchronousServiceTask.java
@@ -17,7 +17,6 @@
 package com.android.jack.server.tasks;
 
 import com.android.jack.server.JackHttpServer;
-import com.android.jack.server.JackHttpServer.ServerClosedException;
 
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
@@ -47,9 +46,6 @@
     try {
       long taskId = jackServer.startingServiceTask();
       handle(taskId, request, response);
-    } catch (ServerClosedException e) {
-      response.setContentLength(0);
-      response.setStatus(Status.SERVICE_UNAVAILABLE);
     } catch (Error | RuntimeException e) {
       logger.log(Level.SEVERE, e.getMessage(), e);
       response.setContentLength(0);