From: aclement Date: Thu, 9 Mar 2006 17:24:19 +0000 (+0000) Subject: 101411: -XaddSerialVersionUID X-Git-Tag: POST_MEMORY_CHANGES X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=6e6658a5e9e1665d18f3b4eb8d94609592b56228;p=aspectj.git 101411: -XaddSerialVersionUID --- diff --git a/bcel-builder/src/org/aspectj/apache/bcel/generic/ClassGen.java b/bcel-builder/src/org/aspectj/apache/bcel/generic/ClassGen.java index bec436bf6..9e21b271e 100644 --- a/bcel-builder/src/org/aspectj/apache/bcel/generic/ClassGen.java +++ b/bcel-builder/src/org/aspectj/apache/bcel/generic/ClassGen.java @@ -54,7 +54,14 @@ package org.aspectj.apache.bcel.generic; * . */ +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; @@ -77,8 +84,10 @@ import org.aspectj.apache.bcel.generic.annotation.AnnotationGen; * 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 M. Dahm + * + * Upgraded, Andy Clement 9th Mar 06 - calculates SUID */ public class ClassGen extends AccessFlags implements Cloneable { /* Corresponds to the fields found in a JavaClass object. @@ -538,4 +547,153 @@ public class ClassGen extends AccessFlags implements Cloneable { 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("")) { + hasClinit=true; + } else if (couldBeInitializer && m.getName().equals("")) { + 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, . + // 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(""); + 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()); // + 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()); + } + } } diff --git a/docs/devGuideDB/ajc.xml b/docs/devGuideDB/ajc.xml index 109bac741..30ca3b901 100644 --- a/docs/devGuideDB/ajc.xml +++ b/docs/devGuideDB/ajc.xml @@ -431,6 +431,16 @@ + + -XaddSerialVersionUID + 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. + + + -Xreweavable[:compress] (Experimental - deprecated as now default) diff --git a/lib/bcel/bcel-src.zip b/lib/bcel/bcel-src.zip index 68ddd5502..770d77312 100644 Binary files a/lib/bcel/bcel-src.zip and b/lib/bcel/bcel-src.zip differ diff --git a/lib/bcel/bcel.jar b/lib/bcel/bcel.jar index e96515cb9..d6237720c 100644 Binary files a/lib/bcel/bcel.jar and b/lib/bcel/bcel.jar differ diff --git a/org.aspectj.ajdt.core/src/org/aspectj/ajdt/ajc/BuildArgParser.java b/org.aspectj.ajdt.core/src/org/aspectj/ajdt/ajc/BuildArgParser.java index fccb824ee..f2749997d 100644 --- a/org.aspectj.ajdt.core/src/org/aspectj/ajdt/ajc/BuildArgParser.java +++ b/org.aspectj.ajdt.core/src/org/aspectj/ajdt/ajc/BuildArgParser.java @@ -533,6 +533,8 @@ public class BuildArgParser extends Main { 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")) { diff --git a/org.aspectj.ajdt.core/src/org/aspectj/ajdt/ajc/messages.properties b/org.aspectj.ajdt.core/src/org/aspectj/ajdt/ajc/messages.properties index 16111cb0f..16e6e4eaf 100644 --- a/org.aspectj.ajdt.core/src/org/aspectj/ajdt/ajc/messages.properties +++ b/org.aspectj.ajdt.core/src/org/aspectj/ajdt/ajc/messages.properties @@ -132,6 +132,8 @@ xoption.usage = {0} non-standard options:\n\ \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: 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\ diff --git a/org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjBuildConfig.java b/org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjBuildConfig.java index c616cfeb0..472180fd6 100644 --- a/org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjBuildConfig.java +++ b/org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjBuildConfig.java @@ -388,6 +388,7 @@ public class AjBuildConfig { } setOutxmlName(global.getOutxmlName()); setXconfigurationInfo(global.getXconfigurationInfo()); + setAddSerialVerUID(global.isAddSerialVerUID()); } void join(Collection local, Collection global) { @@ -523,6 +524,13 @@ public class AjBuildConfig { public boolean isXdevPinpoint() { return options.xdevPinpoint; } + + public void setAddSerialVerUID(boolean b) { + options.addSerialVerUID = b; + } + public boolean isAddSerialVerUID() { + return options.addSerialVerUID; + } public boolean isXNotReweavable() { diff --git a/org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjBuildManager.java b/org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjBuildManager.java index d2a7e88c8..f46dbd4d4 100644 --- a/org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjBuildManager.java +++ b/org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjBuildManager.java @@ -633,6 +633,7 @@ public class AjBuildManager implements IOutputClassFileNameProvider,IBinarySourc 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()); diff --git a/org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjCompilerOptions.java b/org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjCompilerOptions.java index 54a271c27..6407b6da7 100644 --- a/org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjCompilerOptions.java +++ b/org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjCompilerOptions.java @@ -80,6 +80,7 @@ public class AjCompilerOptions extends CompilerOptions { 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; diff --git a/tests/features151/serialveruid/AnAspect.java b/tests/features151/serialveruid/AnAspect.java new file mode 100644 index 000000000..eff8137e8 --- /dev/null +++ b/tests/features151/serialveruid/AnAspect.java @@ -0,0 +1,5 @@ +aspect AnAspect { + before(): staticinitialization(BigHorribleClass) { + + } +} \ No newline at end of file diff --git a/tests/features151/serialveruid/Basic.java b/tests/features151/serialveruid/Basic.java new file mode 100644 index 000000000..f2093d59b --- /dev/null +++ b/tests/features151/serialveruid/Basic.java @@ -0,0 +1,19 @@ +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 diff --git a/tests/features151/serialveruid/BigHorribleClass.java b/tests/features151/serialveruid/BigHorribleClass.java new file mode 100644 index 000000000..7668307b2 --- /dev/null +++ b/tests/features151/serialveruid/BigHorribleClass.java @@ -0,0 +1,28 @@ +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 diff --git a/tests/src/org/aspectj/systemtest/ajc151/AllTestsAspectJ151.java b/tests/src/org/aspectj/systemtest/ajc151/AllTestsAspectJ151.java index a3929851a..8f64f29d8 100644 --- a/tests/src/org/aspectj/systemtest/ajc151/AllTestsAspectJ151.java +++ b/tests/src/org/aspectj/systemtest/ajc151/AllTestsAspectJ151.java @@ -21,6 +21,7 @@ public class AllTestsAspectJ151 { suite.addTest(Ajc151Tests.suite()); suite.addTest(NewarrayJoinpointTests.suite()); suite.addTest(AtAroundTests.suite()); + suite.addTest(SerialVersionUIDTests.suite()); //$JUnit-END$ return suite; } diff --git a/tests/src/org/aspectj/systemtest/ajc151/SerialVersionUIDTests.java b/tests/src/org/aspectj/systemtest/ajc151/SerialVersionUIDTests.java new file mode 100644 index 000000000..fc0c0d276 --- /dev/null +++ b/tests/src/org/aspectj/systemtest/ajc151/SerialVersionUIDTests.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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"); + } + +} diff --git a/tests/src/org/aspectj/systemtest/ajc151/serialversionuid.xml b/tests/src/org/aspectj/systemtest/ajc151/serialversionuid.xml new file mode 100644 index 000000000..c18adaed7 --- /dev/null +++ b/tests/src/org/aspectj/systemtest/ajc151/serialversionuid.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/weaver/src/org/aspectj/weaver/Lint.java b/weaver/src/org/aspectj/weaver/Lint.java index 2ca50ad85..f2542367c 100644 --- a/weaver/src/org/aspectj/weaver/Lint.java +++ b/weaver/src/org/aspectj/weaver/Lint.java @@ -114,6 +114,9 @@ public class Lint { 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 = diff --git a/weaver/src/org/aspectj/weaver/World.java b/weaver/src/org/aspectj/weaver/World.java index 5f9cf6d01..870ba7f43 100644 --- a/weaver/src/org/aspectj/weaver/World.java +++ b/weaver/src/org/aspectj/weaver/World.java @@ -90,6 +90,9 @@ public abstract class World implements Dump.INode { /** 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 @@ -1061,6 +1064,9 @@ public abstract class World implements Dump.INode { 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()); diff --git a/weaver/src/org/aspectj/weaver/XlintDefault.properties b/weaver/src/org/aspectj/weaver/XlintDefault.properties index bef407969..70063a74e 100644 --- a/weaver/src/org/aspectj/weaver/XlintDefault.properties +++ b/weaver/src/org/aspectj/weaver/XlintDefault.properties @@ -40,3 +40,4 @@ cantFindTypeAffectingJPMatch = warning unorderedAdviceAtShadow=ignore swallowedExceptionInCatchBlock=warning +calculatingSerialVersionUID=ignore \ No newline at end of file diff --git a/weaver/src/org/aspectj/weaver/bcel/LazyClassGen.java b/weaver/src/org/aspectj/weaver/bcel/LazyClassGen.java index 3d485ab02..6533ea131 100644 --- a/weaver/src/org/aspectj/weaver/bcel/LazyClassGen.java +++ b/weaver/src/org/aspectj/weaver/bcel/LazyClassGen.java @@ -39,6 +39,7 @@ import org.aspectj.apache.bcel.classfile.Method; 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; @@ -69,6 +70,7 @@ import org.aspectj.weaver.WeaverStateInfo; import org.aspectj.weaver.World; import org.aspectj.weaver.AjAttribute.WeaverVersionInfo; + /** * Lazy lazy lazy. * We don't unpack the underlying class unless necessary. Things @@ -99,6 +101,8 @@ public final class LazyClassGen { private boolean isSerializable = false; private boolean hasSerialVersionUIDField = false; + private boolean serialVersionUIDRequiresInitialization = false; + private long calculatedSerialVersionUID; private boolean hasClinit = false; // --- @@ -279,12 +283,28 @@ public final class LazyClassGen { 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) { @@ -935,10 +955,21 @@ public final class LazyClassGen { // } 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); }