* <http://www.apache.org/>.
*/
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.lang.reflect.Modifier;
+import java.security.MessageDigest;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
* existing java class (file).
*
* @see JavaClass
- * @version $Id: ClassGen.java,v 1.5 2005/03/10 12:15:04 aclement Exp $
+ * @version $Id: ClassGen.java,v 1.6 2006/03/09 17:25:48 aclement Exp $
* @author <A HREF="mailto:markus.dahm@berlin.de">M. Dahm</A>
+ *
+ * Upgraded, Andy Clement 9th Mar 06 - calculates SUID
*/
public class ClassGen extends AccessFlags implements Cloneable {
/* Corresponds to the fields found in a JavaClass object.
public final boolean isEnum() {
return (access_flags & Constants.ACC_ENUM) != 0;
}
+
+ /**
+ * Calculate the SerialVersionUID for a class.
+ */
+ public long getSUID() {
+ try {
+ Field[] fields = getFields();
+ Method[] methods = getMethods();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+
+ // 1. classname
+ dos.writeUTF(getClassName());
+
+ // 2. classmodifiers: ACC_PUBLIC, ACC_FINAL, ACC_INTERFACE, and ACC_ABSTRACT
+ int classmods = 0;
+ classmods|=(isPublic()?Constants.ACC_PUBLIC:0);
+ classmods|=(isFinal()?Constants.ACC_FINAL:0);
+ classmods|=(isInterface()?Constants.ACC_INTERFACE:0);
+ if (isInterface() && isAbstract()) { // remove abstract if we have it but have no methods
+ if (methods.length>0) classmods|=Constants.ACC_ABSTRACT;
+ }
+ dos.writeInt(classmods);
+
+ // 3. ordered list of interfaces
+ List list = new ArrayList();
+ String[] names = getInterfaceNames();
+ if (names!=null) {
+ Arrays.sort(names);
+ for (int i = 0; i < names.length; i++) dos.writeUTF(names[i]);
+ }
+
+ // 4. ordered list of fields (ignoring private static and private transient fields):
+ // (relevant modifiers are ACC_PUBLIC, ACC_PRIVATE,
+ // ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_VOLATILE,
+ // ACC_TRANSIENT)
+ list.clear();
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ if (!(field.isPrivate() && field.isStatic()) &&
+ !(field.isPrivate() && field.isTransient())) list.add(field);
+ }
+ Collections.sort(list,new FieldComparator());
+ int relevantFlags = Constants.ACC_PUBLIC | Constants.ACC_PRIVATE | Constants.ACC_PROTECTED |
+ Constants.ACC_STATIC | Constants.ACC_FINAL | Constants.ACC_VOLATILE | Constants.ACC_TRANSIENT;
+ for (Iterator iter = list.iterator(); iter.hasNext();) {
+ Field f = (Field) iter.next();
+ dos.writeUTF(f.getName());
+ dos.writeInt(relevantFlags&f.getModifiers());
+ dos.writeUTF(f.getType().getSignature());
+ }
+
+ // some up front method processing: discover clinit, init and ordinary methods of interest:
+ list.clear(); // now used for methods
+ List ctors = new ArrayList();
+ boolean hasClinit = false;
+ for (int i = 0; i < methods.length; i++) {
+ Method m = methods[i];
+ boolean couldBeInitializer = m.getName().charAt(0)=='<';
+ if (couldBeInitializer && m.getName().equals("<clinit>")) {
+ hasClinit=true;
+ } else if (couldBeInitializer && m.getName().equals("<init>")) {
+ if (!m.isPrivate()) ctors.add(m);
+ } else {
+ if (!m.isPrivate()) list.add(m);
+ }
+ }
+ Collections.sort(ctors, new ConstructorComparator());
+ Collections.sort(list, new MethodComparator());
+
+
+ // 5. If a class initializer exists, write out the following:
+ // 1. The name of the method, <clinit>.
+ // 2. The modifier of the method, java.lang.reflect.Modifier.STATIC, written as a 32-bit integer.
+ // 3. The descriptor of the method, ()V.
+ if (hasClinit) {
+ dos.writeUTF("<clinit>");
+ dos.writeInt(Modifier.STATIC);
+ dos.writeUTF("()V");
+ }
+
+ // for methods and constructors:
+ // ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_SYNCHRONIZED,
+ // ACC_NATIVE, ACC_ABSTRACT and ACC_STRICT
+ relevantFlags =
+ Constants.ACC_PUBLIC | Constants.ACC_PRIVATE | Constants.ACC_PROTECTED |
+ Constants.ACC_STATIC | Constants.ACC_FINAL | Constants.ACC_SYNCHRONIZED |
+ Constants.ACC_NATIVE | Constants.ACC_ABSTRACT | Constants.ACC_STRICT;
+
+ // 6. sorted non-private constructors
+ for (Iterator iter = ctors.iterator(); iter.hasNext();) {
+ Method m = (Method) iter.next();
+ dos.writeUTF(m.getName()); // <init>
+ dos.writeInt(relevantFlags & m.getModifiers());
+ dos.writeUTF(m.getSignature().replace('/','.'));
+ }
+
+ // 7. sorted non-private methods
+ for (Iterator iter = list.iterator(); iter.hasNext();) {
+ Method m = (Method) iter.next();
+ dos.writeUTF(m.getName());
+ dos.writeInt(relevantFlags & m.getModifiers());
+ dos.writeUTF(m.getSignature().replace('/','.'));
+ }
+ dos.flush();
+ dos.close();
+ byte[] bs = baos.toByteArray();
+ MessageDigest md = MessageDigest.getInstance("SHA");
+ byte[] result = md.digest(bs);
+
+ long suid = 0L;
+ int pos = result.length>8?7:result.length-1; // use the bytes we have
+ while (pos>=0) {
+ suid = suid<<8 | ((long)result[pos--]&0xff);
+ }
+
+ // if it was definetly 8 everytime...
+ // long suid = ((long)(sha[0]&0xff) | (long)(sha[1]&0xff) << 8 |
+ // (long)(sha[2]&0xff) << 16 | (long)(sha[3]&0xff) << 24 |
+ // (long)(sha[4]&0xff) << 32 | (long)(sha[5]&0xff) << 40 |
+ // (long)(sha[6]&0xff) << 48 | (long)(sha[7]&0xff) << 56);
+ return suid;
+ } catch (Exception e) {
+ System.err.println("Unable to calculate suid for "+getClassName());
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static class FieldComparator implements Comparator {
+ public int compare(Object arg0, Object arg1) {
+ return ((Field)arg0).getName().compareTo(((Field)arg1).getName());
+ }
+ }
+ private static class ConstructorComparator implements Comparator {
+ public int compare(Object arg0, Object arg1) {
+ // can ignore the name...
+ return ((Method)arg0).getSignature().compareTo(((Method)arg1).getSignature());
+ }
+ }
+ private static class MethodComparator implements Comparator {
+ public int compare(Object arg0, Object arg1) {
+ Method m1 = (Method)arg0;
+ Method m2 = (Method)arg1;
+ int result = m1.getName().compareTo(m2.getName());
+ if (result!=0) return result;
+ return m1.getSignature().compareTo(m2.getSignature());
+ }
+ }
}
</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term>-XaddSerialVersionUID</term>
+ <listitem><para>Causes the compiler to calculate and add
+ the SerialVersionUID field to any type implementing
+ Serializable that is affected by an aspect. The field
+ is calculated based on the class before weaving has
+ taken place.
+ </para></listitem>
+ </varlistentry>
+
<varlistentry>
<term>-Xreweavable[:compress]</term>
<listitem><para>(Experimental - deprecated as now default)
buildConfig.setGenerateJavadocsInModelMode(true);
} else if (arg.equals("-Xdev:NoAtAspectJProcessing")) {
buildConfig.setNoAtAspectJAnnotationProcessing(true);
+ } else if (arg.equals("-XaddSerialVersionUID")) {
+ buildConfig.setAddSerialVerUID(true);
} else if (arg.equals("-Xdev:Pinpoint")) {
buildConfig.setXdevPinpointMode(true);
} else if (arg.equals("-Xjoinpoints:arrayconstruction")) {
\t by AspectJ.\n\
\t-XserializableAspects allows aspects to implement serializable\n\
\t-XterminateAfterCompilation compile classes then terminate before weaving\n\
+\t-XaddSerialVersionUID calculates and adds the serialVersionUID to any\n\
+\t serializable type woven by an aspect\n\
\t-Xajruntimelevel:<level> allows code to be generated that targets\n\
\t a 1.2 or a 1.5 level AspectJ runtime (default 1.5)\n\
\t-XhasMember allow hasmethod() and hasfield type patterns in\n\
}
setOutxmlName(global.getOutxmlName());
setXconfigurationInfo(global.getXconfigurationInfo());
+ setAddSerialVerUID(global.isAddSerialVerUID());
}
void join(Collection local, Collection global) {
public boolean isXdevPinpoint() {
return options.xdevPinpoint;
}
+
+ public void setAddSerialVerUID(boolean b) {
+ options.addSerialVerUID = b;
+ }
+ public boolean isAddSerialVerUID() {
+ return options.addSerialVerUID;
+ }
public boolean isXNotReweavable() {
cp.addAll(buildConfig.getClasspath());
BcelWorld bcelWorld = new BcelWorld(cp, handler, null);
bcelWorld.setBehaveInJava5Way(buildConfig.getBehaveInJava5Way());
+ bcelWorld.setAddSerialVerUID(buildConfig.isAddSerialVerUID());
bcelWorld.performExtraConfiguration(buildConfig.getXconfigurationInfo());
bcelWorld.setTargetAspectjRuntimeLevel(buildConfig.getTargetAspectjRuntimeLevel());
bcelWorld.setOptionalJoinpoints(buildConfig.getXJoinpoints());
public String targetAspectjRuntimeLevel = Constants.RUNTIME_LEVEL_DEFAULT;
public String xConfigurationInfo;
+ public boolean addSerialVerUID = false;
// these next four not exposed by IDEs
public boolean generateModel = false;
--- /dev/null
+aspect AnAspect {
+ before(): staticinitialization(BigHorribleClass) {
+
+ }
+}
\ No newline at end of file
--- /dev/null
+import java.io.Serializable;
+import java.lang.reflect.Field;
+
+public class Basic implements Serializable {
+ public static void main(String[] args) {
+ try {
+ Basic b = (Basic)Basic.class.newInstance();
+ Field f = Basic.class.getDeclaredField("serialVersionUID");
+ long l = f.getLong(b);
+ System.err.println("SerialVersionUID is "+l);
+ } catch (Exception e) {
+ System.err.println("Problem: "+e.toString());
+ }
+ }
+}
+
+aspect X {
+ before(): staticinitialization(Basic) {}
+}
\ No newline at end of file
--- /dev/null
+import java.io.Serializable;
+import java.lang.reflect.Field;
+
+public class BigHorribleClass implements Serializable,Comparable {
+ public static void main(String[] args) {
+ try {
+ BigHorribleClass b = (BigHorribleClass)BigHorribleClass.class.newInstance();
+ Field f = BigHorribleClass.class.getDeclaredField("serialVersionUID");
+ long l = f.getLong(b);
+ System.err.println("SerialVersionUID is "+l);
+ } catch (Exception e) {
+ System.err.println("Problem: "+e.toString());
+ }
+ }
+
+ public int anInt;
+
+ public static boolean aBoolean = false;
+
+ public long foo = 376;
+
+ public void m() {}
+ public int compareTo(Object o) { return 0;}
+ public String m2(boolean b,long l, String s) { return "";}
+
+ public static transient short fo2 = 3;
+
+}
\ No newline at end of file
suite.addTest(Ajc151Tests.suite());
suite.addTest(NewarrayJoinpointTests.suite());
suite.addTest(AtAroundTests.suite());
+ suite.addTest(SerialVersionUIDTests.suite());
//$JUnit-END$
return suite;
}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Andy Clement - initial implementation
+ *******************************************************************************/
+package org.aspectj.systemtest.ajc151;
+
+import java.io.File;
+
+import junit.framework.Test;
+
+import org.aspectj.testing.XMLBasedAjcTestCase;
+
+
+public class SerialVersionUIDTests extends XMLBasedAjcTestCase {
+
+ public void testTheBasics() { runTest("basic"); }
+ public void testTheBasicsWithLint() { runTest("basic - lint"); }
+ public void testHorrible() { runTest("horrible"); }
+
+ //
+ public static Test suite() {
+ return XMLBasedAjcTestCase.loadSuite(SerialVersionUIDTests.class);
+ }
+
+ protected File getSpecFile() {
+ return new File("../tests/src/org/aspectj/systemtest/ajc151/serialversionuid.xml");
+ }
+
+}
--- /dev/null
+<!DOCTYPE suite SYSTEM "../tests/ajcTestSuite.dtd"[]>
+
+<!-- AspectJ v1.5.1 Tests -->
+<suite>
+
+ <ajc-test dir="features151/serialveruid" title="basic">
+ <compile files="Basic.java" options="-1.5 -XaddSerialVersionUID"/>
+ <run class="Basic">
+ <stderr>
+ <line text="SerialVersionUID is 7052682057082172300"/>
+ </stderr>
+ </run>
+ </ajc-test>
+
+ <ajc-test dir="features151/serialveruid" title="basic - lint">
+ <compile files="Basic.java" options="-1.5 -XaddSerialVersionUID -Xlint:warning">
+ <message kind="warning" text="calculated SerialVersionUID for type Basic"/>
+ </compile>
+ <run class="Basic">
+ <stderr>
+ <line text="SerialVersionUID is 7052682057082172300"/>
+ </stderr>
+ </run>
+ </ajc-test>
+
+ <ajc-test dir="features151/serialveruid" title="horrible">
+ <compile files="BigHorribleClass.java,AnAspect.java" options="-1.5 -XaddSerialVersionUID -Xlint:warning">
+ <message kind="warning" text="calculated SerialVersionUID for type BigHorribleClass"/>
+ </compile>
+ <run class="BigHorribleClass">
+ <stderr>
+ <line text="SerialVersionUID is 6512414869923012873"/>
+ </stderr>
+ </run>
+ </ajc-test>
+
+</suite>
\ No newline at end of file
public final Kind swallowedExceptionInCatchBlock =
new Kind("swallowedExceptionInCatchBlock","exception swallowed in catch block");
+ public final Kind calculatingSerialVersionUID =
+ new Kind("calculatingSerialVersionUID","calculated SerialVersionUID for type {0} to be {1}");
+
// there are a lot of messages in the cant find type family - I'm defining an umbrella lint warning that
// allows a user to control their severity (for e.g. ltw or binary weaving)
public final Kind cantFindType =
/** Flags for the new joinpoints that are 'optional' */
private boolean optionalJoinpoint_ArrayConstruction = false; // Command line flag: "arrayconstruction"
+ private boolean addSerialVerUID = false;
+
+
private Properties extraConfiguration = null;
// Records whether ASM is around ... so we might use it for delegates
workInProgress1.remove(baseClass);
}
+ public void setAddSerialVerUID(boolean b) { addSerialVerUID=b;}
+ public boolean isAddSerialVerUID() { return addSerialVerUID;}
+
public void flush() {
// System.err.println("BEFORE FLUSHING");
// System.err.println(typeMap.toString());
unorderedAdviceAtShadow=ignore
swallowedExceptionInCatchBlock=warning
+calculatingSerialVersionUID=ignore
\ No newline at end of file
import org.aspectj.apache.bcel.classfile.Signature;
import org.aspectj.apache.bcel.classfile.Unknown;
import org.aspectj.apache.bcel.classfile.annotation.Annotation;
+import org.aspectj.apache.bcel.generic.BasicType;
import org.aspectj.apache.bcel.generic.ClassGen;
import org.aspectj.apache.bcel.generic.ConstantPoolGen;
import org.aspectj.apache.bcel.generic.FieldGen;
import org.aspectj.weaver.World;
import org.aspectj.weaver.AjAttribute.WeaverVersionInfo;
+
/**
* Lazy lazy lazy.
* We don't unpack the underlying class unless necessary. Things
private boolean isSerializable = false;
private boolean hasSerialVersionUIDField = false;
+ private boolean serialVersionUIDRequiresInitialization = false;
+ private long calculatedSerialVersionUID;
private boolean hasClinit = false;
// ---
hasClinit = true;
}
}
+
+ // Do we need to calculate an SUID and add it?
+ if (!hasSerialVersionUIDField && world.isAddSerialVerUID()) {
+ calculatedSerialVersionUID = myGen.getSUID();
+ Field fg = new FieldGen(
+ Constants.ACC_PRIVATE|Constants.ACC_FINAL|Constants.ACC_STATIC,
+ BasicType.LONG,"serialVersionUID",getConstantPoolGen()).getField();
+ addField(fg);
+ hasSerialVersionUIDField=true;
+ serialVersionUIDRequiresInitialization=true;
+ // warn about what we've done?
+ if (world.getLint().calculatingSerialVersionUID.isEnabled())
+ world.getLint().calculatingSerialVersionUID.signal(
+ new String[]{getClassName(),Long.toString(calculatedSerialVersionUID)+"L"},null,null);
+ }
}
Method[] methods = myGen.getMethods();
for (int i = 0; i < methods.length; i++) {
addMethodGen(new LazyMethodGen(methods[i], this));
}
+
}
public static boolean hasSerialVersionUIDField (ResolvedType type) {
// }
private void addAjcInitializers() {
- if (tjpFields.size() == 0) return;
+ if (tjpFields.size() == 0 && !serialVersionUIDRequiresInitialization) return;
+ InstructionList il = null;
+
+ if (tjpFields.size()>0) {
+ il = initializeAllTjps();
+ }
+
+ if (serialVersionUIDRequiresInitialization) {
+ if (il==null) {
+ il= new InstructionList();
+ }
+ il.append(new PUSH(getConstantPoolGen(),calculatedSerialVersionUID));
+ il.append(getFactory().createFieldAccess(getClassName(), "serialVersionUID", BasicType.LONG, Constants.PUTSTATIC));
+ }
- InstructionList il = initializeAllTjps();
-
getStaticInitializer().getBody().insert(il);
}