]> source.dussan.org Git - aspectj.git/commitdiff
more overweaving tests and fixes: method call/field get/set
authoraclement <aclement>
Thu, 13 May 2010 04:05:25 +0000 (04:05 +0000)
committeraclement <aclement>
Thu, 13 May 2010 04:05:25 +0000 (04:05 +0000)
weaver/src/org/aspectj/weaver/bcel/BcelClassWeaver.java
weaver/src/org/aspectj/weaver/bcel/BcelWeaver.java
weaver/src/org/aspectj/weaver/bcel/LazyClassGen.java
weaver/src/org/aspectj/weaver/bcel/LazyMethodGen.java

index f13d2b69e62b5ef34f6317d05c13062a381a0778..1bef5f9684bc38b819c67f68e69f47bcbf9c3a95 100644 (file)
@@ -92,10 +92,6 @@ class BcelClassWeaver implements IClassWeaver {
 
        private static Trace trace = TraceFactory.getTraceFactory().getTrace(BcelClassWeaver.class);
 
-       /**
-        * This is called from {@link BcelWeaver} to perform the per-class weaving process.
-        * 
-        */
        public static boolean weave(BcelWorld world, LazyClassGen clazz, List<ShadowMunger> shadowMungers,
                        List<ConcreteTypeMunger> typeMungers, List lateTypeMungers, boolean inReweavableMode) {
                BcelClassWeaver classWeaver = new BcelClassWeaver(world, clazz, shadowMungers, typeMungers, lateTypeMungers);
@@ -2503,7 +2499,7 @@ class BcelClassWeaver implements IClassWeaver {
        private boolean match(LazyMethodGen mg) {
                BcelShadow enclosingShadow;
                List<BcelShadow> shadowAccumulator = new ArrayList<BcelShadow>();
-
+               boolean isOverweaving = world.isOverWeaving();
                boolean startsAngly = mg.getName().charAt(0) == '<';
                // we want to match ajsynthetic constructors...
                if (startsAngly && mg.getName().equals("<init>")) {
@@ -2520,6 +2516,10 @@ class BcelClassWeaver implements IClassWeaver {
                        } else {
                                AjAttribute.EffectiveSignatureAttribute effective = mg.getEffectiveSignature();
                                if (effective == null) {
+                                       // Don't want ajc$preClinit to be considered for matching
+                                       if (isOverweaving && mg.getName().startsWith(NameMangler.PREFIX)) {
+                                               return false;
+                                       }
                                        enclosingShadow = BcelShadow.makeMethodExecution(world, mg, !canMatchBodyShadows);
                                } else if (effective.isWeaveBody()) {
                                        ResolvedMember rm = effective.getEffectiveSignature();
@@ -3043,7 +3043,23 @@ class BcelClassWeaver implements IClassWeaver {
                        }
                } else {
                        if (canMatch(Shadow.MethodCall)) {
-                               match(BcelShadow.makeMethodCall(world, mg, ih, enclosingShadow), shadowAccumulator);
+                               boolean proceed = true;
+                               // overweaving needs to ignore some calls added by the previous weave
+                               if (world.isOverWeaving()) {
+                                       String s = invoke.getClassName(mg.getConstantPool());
+                                       // skip all the inc/dec/isValid/etc
+                                       if (s.equals("org.aspectj.runtime.internal.CFlowCounter")
+                                                       || s.equals("org.aspectj.runtime.internal.CFlowStack")) {
+                                               proceed = false;
+                                       } else {
+                                               if (methodName.equals("aspectOf")) {
+                                                       proceed = false;
+                                               }
+                                       }
+                               }
+                               if (proceed) {
+                                       match(BcelShadow.makeMethodCall(world, mg, ih, enclosingShadow), shadowAccumulator);
+                               }
                        }
                }
        }
index 0622a51701da13ae2c281b164916be56e640f929..8ca8ae963926094a1e9b63d9e993263ccbf9fabb 100644 (file)
@@ -1,16 +1,11 @@
 /* *******************************************************************
- * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
+ * Copyright (c) 2002-2010 Contributors
  * All rights reserved.
  * This program and the accompanying materials are made available
  * under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *     PARC     initial implementation
- *     Alexandre Vasseur    support for @AJ aspects
  * ******************************************************************/
-
 package org.aspectj.weaver.bcel;
 
 import java.io.ByteArrayInputStream;
@@ -96,6 +91,12 @@ import org.aspectj.weaver.patterns.WithinPointcut;
 import org.aspectj.weaver.tools.Trace;
 import org.aspectj.weaver.tools.TraceFactory;
 
+/**
+ * 
+ * @author PARC
+ * @author Andy Clement
+ * @author Alexandre Vasseur
+ */
 public class BcelWeaver {
 
        public static final String CLOSURE_CLASS_PREFIX = "$Ajc";
@@ -108,14 +109,14 @@ public class BcelWeaver {
 
        private boolean inReweavableMode = false;
 
-       // private Map /*String,UnwovenClassFile*/sourceJavaClasses = new HashMap();
-       // private Map /*String,UnwovenClassFile*/resources = new HashMap();
        private transient List<UnwovenClassFile> addedClasses = new ArrayList<UnwovenClassFile>();
        private transient List<String> deletedTypenames = new ArrayList<String>();
-       private transient List<ShadowMunger> shadowMungerList = null; // setup by prepareForWeave
-       private transient List typeMungerList = null; // setup by prepareForWeave
-       private transient List lateTypeMungerList = null; // setup by prepareForWeave
-       private transient List declareParentsList = null; // setup by prepareForWeave
+
+       // These four are setup by prepareForWeave
+       private transient List<ShadowMunger> shadowMungerList = null;
+       private transient List typeMungerList = null;
+       private transient List lateTypeMungerList = null;
+       private transient List declareParentsList = null;
 
        private Manifest manifest = null;
        private boolean needToReweaveWorld = false;
@@ -125,8 +126,6 @@ public class BcelWeaver {
        private ZipOutputStream zipOutputStream;
        private CustomMungerFactory customMungerFactory;
 
-       // ---
-
        public BcelWeaver(BcelWorld world) {
                super();
                if (trace.isTraceEnabled()) {
@@ -139,11 +138,6 @@ public class BcelWeaver {
                }
        }
 
-       // only called for testing
-       public void setShadowMungers(List shadowMungers) {
-               shadowMungerList = shadowMungers;
-       }
-
        /**
         * Add the given aspect to the weaver. The type is resolved to support DOT for static inner classes as well as DOLLAR
         * 
@@ -230,8 +224,7 @@ public class BcelWeaver {
 
        /**
         * 
-        * @param inFile File path to class directory or zip/jar class archive
-        * @throws IOException
+        * @param inFile directory containing classes or zip/jar class archive
         */
        public void addLibraryJarFile(File inFile) throws IOException {
                List<ResolvedType> addedAspects = null;
@@ -243,9 +236,8 @@ public class BcelWeaver {
                if (world.isOverWeaving()) {
                        return;
                }
-               for (Iterator<ResolvedType> i = addedAspects.iterator(); i.hasNext();) {
-                       ResolvedType aspectX = i.next();
-                       xcutSet.addOrReplaceAspect(aspectX);
+               for (ResolvedType addedAspect : addedAspects) {
+                       xcutSet.addOrReplaceAspect(addedAspect);
                }
        }
 
@@ -282,32 +274,50 @@ public class BcelWeaver {
                return addedAspects;
        }
 
-       private List<ResolvedType> addAspectsFromDirectory(File dir) throws FileNotFoundException, IOException {
+       /**
+        * Look for .class files that represent aspects in the supplied directory - return the list of accumulated aspects.
+        * 
+        * @param directory the directory in which to look for Aspect .class files
+        * @return the list of discovered aspects
+        * @throws FileNotFoundException
+        * @throws IOException
+        */
+       private List<ResolvedType> addAspectsFromDirectory(File directory) throws FileNotFoundException, IOException {
                List<ResolvedType> addedAspects = new ArrayList<ResolvedType>();
-               File[] classFiles = FileUtil.listFiles(dir, new FileFilter() {
-
+               File[] classFiles = FileUtil.listFiles(directory, new FileFilter() {
                        public boolean accept(File pathname) {
                                return pathname.getName().endsWith(".class");
                        }
-
                });
-               for (int i = 0; i < classFiles.length; i++) {
-                       FileInputStream fis = new FileInputStream(classFiles[i]);
-                       byte[] bytes = FileUtil.readAsByteArray(fis);
-                       addIfAspect(bytes, classFiles[i].getAbsolutePath(), addedAspects, dir);
+               for (File classFile : classFiles) {
+                       FileInputStream fis = new FileInputStream(classFile);
+                       byte[] classBytes = FileUtil.readAsByteArray(fis);
+                       ResolvedType aspectType = isAspect(classBytes, classFile.getAbsolutePath(), directory);
+                       if (aspectType != null) {
+                               addedAspects.add(aspectType);
+                       }
                        fis.close();
                }
                return addedAspects;
        }
 
-       private void addIfAspect(byte[] bytes, String name, List<ResolvedType> toList, File dir) throws IOException {
-               ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), name);
+       /**
+        * Determine if the supplied bytes represent an aspect, if they do then create a ResolvedType instance for the aspect and return
+        * it, otherwise return null
+        * 
+        * @param classbytes the classbytes that might represent an aspect
+        * @param name the name of the class
+        * @param directory directory which contained the class file
+        * @return a ResolvedType if the classbytes represent an aspect, otherwise null
+        */
+       private ResolvedType isAspect(byte[] classbytes, String name, File dir) throws IOException {
+               ClassParser parser = new ClassParser(new ByteArrayInputStream(classbytes), name);
                JavaClass jc = parser.parse();
                ResolvedType type = world.addSourceObjectType(jc, false).getResolvedTypeX();
                String typeName = type.getName().replace('.', File.separatorChar);
                int end = name.lastIndexOf(typeName + ".class");
                String binaryPath = null;
-               // if end is -1 then something wierd happened, the class file is not in
+               // if end is -1 then something weird happened, the class file is not in
                // the correct place, something like
                // bin/A.class when the declaration for A specifies it is in a package.
                if (end == -1) {
@@ -316,9 +326,7 @@ public class BcelWeaver {
                        binaryPath = name.substring(0, end - 1);
                }
                type.setBinaryPath(binaryPath);
-               if (type.isAspect()) {
-                       toList.add(type);
-               }
+               return (type.isAspect() ? type : null);
        }
 
        // // The ANT copy task should be used to copy resources across.
@@ -424,25 +432,6 @@ public class BcelWeaver {
                return addedClassFiles;
        }
 
-       // public void addResource(String name, File inPath, File outDir) throws
-       // IOException {
-       //
-       // /* Eliminate CVS files. Relative paths use "/" */
-       // if (!name.startsWith("CVS/") && (-1 == name.indexOf("/CVS/")) &&
-       // !name.endsWith("/CVS")) {
-       // // System.err.println("? addResource('" + name + "')");
-       // // BufferedInputStream inStream = new BufferedInputStream(new
-       // FileInputStream(inPath));
-       // // byte[] bytes = new byte[(int)inPath.length()];
-       // // inStream.read(bytes);
-       // // inStream.close();
-       // byte[] bytes = FileUtil.readAsByteArray(inPath);
-       // UnwovenClassFile resourceFile = new UnwovenClassFile(new File(outDir,
-       // name).getAbsolutePath(), bytes);
-       // addResource(name,resourceFile);
-       // }
-       // }
-
        public boolean needToReweaveWorld() {
                return needToReweaveWorld;
        }
@@ -452,10 +441,6 @@ public class BcelWeaver {
         */
        public ReferenceType addClassFile(UnwovenClassFile classFile, boolean fromInpath) {
                addedClasses.add(classFile);
-               // if (null != sourceJavaClasses.put(classFile.getClassName(),
-               // classFile)) {
-               // // throw new RuntimeException(classFile.getClassName());
-               // }
                ReferenceType type = world.addSourceObjectType(classFile.getJavaClass(), false).getResolvedTypeX();
                if (fromInpath) {
                        type.setBinaryPath(classFile.getFilename());
@@ -489,21 +474,9 @@ public class BcelWeaver {
 
        public void deleteClassFile(String typename) {
                deletedTypenames.add(typename);
-               // sourceJavaClasses.remove(typename);
                world.deleteSourceObjectType(UnresolvedType.forName(typename));
        }
 
-       // public void addResource (String name, UnwovenClassFile resourceFile) {
-       // /* bug-44190 Change error to warning and copy first resource */
-       // if (!resources.containsKey(name)) {
-       // resources.put(name, resourceFile);
-       // }
-       // else {
-       // world.showMessage(IMessage.WARNING, "duplicate resource: '" + name + "'",
-       // null, null);
-       // }
-       // }
-
        // ---- weave preparation
 
        public void setIsBatchWeave(boolean b) {
@@ -618,10 +591,9 @@ public class BcelWeaver {
         * remember their match decision on the last shadow, this makes matching faster when many pointcuts share common elements, or
         * even when one single pointcut has one common element (which can be a side-effect of DNF rewriting).
         */
-       private void rewritePointcuts(List/* ShadowMunger */shadowMungers) {
+       private void rewritePointcuts(List<ShadowMunger> shadowMungers) {
                PointcutRewriter rewriter = new PointcutRewriter();
-               for (Iterator iter = shadowMungers.iterator(); iter.hasNext();) {
-                       ShadowMunger munger = (ShadowMunger) iter.next();
+               for (ShadowMunger munger : shadowMungers) {
                        Pointcut p = munger.getPointcut();
                        Pointcut newP = rewriter.rewrite(p);
                        // validateBindings now whilst we still have around the pointcut
@@ -959,56 +931,6 @@ public class BcelWeaver {
                                .getSourceLocation(), null);
        }
 
-       // public void dumpUnwoven(File file) throws IOException {
-       // BufferedOutputStream os = FileUtil.makeOutputStream(file);
-       // this.zipOutputStream = new ZipOutputStream(os);
-       // dumpUnwoven();
-       // /* BUG 40943*/
-       // dumpResourcesToOutJar();
-       // zipOutputStream.close(); //this flushes and closes the acutal file
-       // }
-       //    
-       //    
-       // public void dumpUnwoven() throws IOException {
-       // Collection filesToDump = new HashSet(sourceJavaClasses.values());
-       // for (Iterator i = filesToDump.iterator(); i.hasNext(); ) {
-       // UnwovenClassFile classFile = (UnwovenClassFile)i.next();
-       // dumpUnchanged(classFile);
-       // }
-       // }
-
-       // public void dumpResourcesToOutPath() throws IOException {
-       // // System.err.println("? dumpResourcesToOutPath() resources=" +
-       // resources.keySet());
-       // Iterator i = resources.keySet().iterator();
-       // while (i.hasNext()) {
-       // UnwovenClassFile res = (UnwovenClassFile)resources.get(i.next());
-       // dumpUnchanged(res);
-       // }
-       // //resources = new HashMap();
-       // }
-       //
-       /* BUG #40943 */
-       // public void dumpResourcesToOutJar() throws IOException {
-       // // System.err.println("? dumpResourcesToOutJar() resources=" +
-       // resources.keySet());
-       // Iterator i = resources.keySet().iterator();
-       // while (i.hasNext()) {
-       // String name = (String)i.next();
-       // UnwovenClassFile res = (UnwovenClassFile)resources.get(name);
-       // writeZipEntry(name,res.getBytes());
-       // }
-       // resources = new HashMap();
-       // }
-       //    
-       // // halfway house for when the jar is managed outside of the weaver, but
-       // the resources
-       // // to be copied are known in the weaver.
-       // public void dumpResourcesToOutJar(ZipOutputStream zos) throws IOException
-       // {
-       // this.zipOutputStream = zos;
-       // dumpResourcesToOutJar();
-       // }
        public void addManifest(Manifest newManifest) {
                // System.out.println("? addManifest() newManifest=" + newManifest);
                if (manifest == null) {
@@ -1016,13 +938,12 @@ public class BcelWeaver {
                }
        }
 
-       private static final String WEAVER_MANIFEST_VERSION = "1.0";
-       private static final Attributes.Name CREATED_BY = new Name("Created-By");
-       private static final String WEAVER_CREATED_BY = "AspectJ Compiler";
-
        public Manifest getManifest(boolean shouldCreate) {
-
                if (manifest == null && shouldCreate) {
+                       String WEAVER_MANIFEST_VERSION = "1.0";
+                       Attributes.Name CREATED_BY = new Name("Created-By");
+                       String WEAVER_CREATED_BY = "AspectJ Compiler";
+
                        manifest = new Manifest();
 
                        Attributes attributes = manifest.getMainAttributes();
@@ -1035,7 +956,7 @@ public class BcelWeaver {
 
        // ---- weaving
 
-       // Used by some test cases only...
+       // FOR TESTING
        public Collection<String> weave(File file) throws IOException {
                OutputStream os = FileUtil.makeOutputStream(file);
                this.zipOutputStream = new ZipOutputStream(os);
@@ -1082,80 +1003,6 @@ public class BcelWeaver {
                return c;
        }
 
-       // public Collection weave() throws IOException {
-       // prepareForWeave();
-       // Collection filesToWeave;
-       //      
-       // if (needToReweaveWorld) {
-       // filesToWeave = sourceJavaClasses.values();
-       // } else {
-       // filesToWeave = addedClasses;
-       // }
-       //      
-       // Collection wovenClassNames = new ArrayList();
-       // world.showMessage(IMessage.INFO, "might need to weave " + filesToWeave +
-       // "(world=" + needToReweaveWorld + ")", null, null);
-       //      
-       //      
-       // //System.err.println("typeMungers: " + typeMungerList);
-       //      
-       // prepareToProcessReweavableState();
-       // // clear all state from files we'll be reweaving
-       // for (Iterator i = filesToWeave.iterator(); i.hasNext(); ) {
-       // UnwovenClassFile classFile = (UnwovenClassFile)i.next();
-       // String className = classFile.getClassName();
-       // BcelObjectType classType = getClassType(className);
-       // processReweavableStateIfPresent(className, classType);
-       // }
-       //      
-       //      
-       //      
-       // //XXX this isn't quite the right place for this...
-       // for (Iterator i = filesToWeave.iterator(); i.hasNext(); ) {
-       // UnwovenClassFile classFile = (UnwovenClassFile)i.next();
-       // String className = classFile.getClassName();
-       // addTypeMungers(className);
-       // }
-       //      
-       // // first weave into aspects
-       // for (Iterator i = filesToWeave.iterator(); i.hasNext(); ) {
-       // UnwovenClassFile classFile = (UnwovenClassFile)i.next();
-       // String className = classFile.getClassName();
-       // BcelObjectType classType =
-       // BcelWorld.getBcelObjectType(world.resolve(className));
-       // if (classType.isAspect()) {
-       // weave(classFile, classType);
-       // wovenClassNames.add(className);
-       // }
-       // }
-       //
-       // // then weave into non-aspects
-       // for (Iterator i = filesToWeave.iterator(); i.hasNext(); ) {
-       // UnwovenClassFile classFile = (UnwovenClassFile)i.next();
-       // String className = classFile.getClassName();
-       // BcelObjectType classType =
-       // BcelWorld.getBcelObjectType(world.resolve(className));
-       // if (! classType.isAspect()) {
-       // weave(classFile, classType);
-       // wovenClassNames.add(className);
-       // }
-       // }
-       //        
-       // if (zipOutputStream != null && !needToReweaveWorld) {
-       // Collection filesToDump = new HashSet(sourceJavaClasses.values());
-       // filesToDump.removeAll(filesToWeave);
-       // for (Iterator i = filesToDump.iterator(); i.hasNext(); ) {
-       // UnwovenClassFile classFile = (UnwovenClassFile)i.next();
-       // dumpUnchanged(classFile);
-       // }
-       // }
-       //        
-       // addedClasses = new ArrayList();
-       // deletedTypenames = new ArrayList();
-       //              
-       // return wovenClassNames;
-       // }
-
        // variation of "weave" that sources class files from an external source.
        public Collection<String> weave(IClassFileProvider input) throws IOException {
                if (trace.isTraceEnabled()) {
@@ -1168,8 +1015,7 @@ public class BcelWeaver {
                for (Iterator<UnwovenClassFile> i = input.getClassFileIterator(); i.hasNext();) {
                        UnwovenClassFile classFile = i.next();
                        if (world.getModel() != null /* AsmManager.isCreatingModel() */&& !isBatchWeave) {
-                               // remove all relationships where this file being woven is the
-                               // target of the relationship
+                               // remove all relationships where this file being woven is the target of the relationship
                                world.getModelAsAsmManager().removeRelationshipsTargettingThisType(classFile.getClassName());
                        }
                }
@@ -1774,7 +1620,7 @@ public class BcelWeaver {
                return weave(classFile, classType, false);
        }
 
-       // non-private for testing
+       // FOR TESTING
        LazyClassGen weave(UnwovenClassFile classFile, BcelObjectType classType) throws IOException {
                LazyClassGen ret = weave(classFile, classType, true);
                return ret;
@@ -2041,4 +1887,9 @@ public class BcelWeaver {
        public void write(DataOutputStream dos) throws IOException {
                xcutSet.write(dos);
        }
+
+       // only called for testing
+       public void setShadowMungers(List shadowMungers) {
+               shadowMungerList = shadowMungers;
+       }
 }
index fd0634ad5871638d4c5df2273e9ab8997492c1b1..af93b758c7521d0055b886bac7287ca555f054b5 100644 (file)
@@ -909,20 +909,29 @@ public final class LazyClassGen {
                return clinit;
        }
 
+       /**
+        * Retrieve the ajc$preClinit method - this method captures any initialization AspectJ wants to ensure happens in a class. It is
+        * called from the static initializer. Maintaining this separation enables overweaving to ignore join points added due to
+        * earlier weaves. If the ajc$preClinit method cannot be found, it is created and a call to it is placed in the real static
+        * initializer (the call is placed at the start of the static initializer).
+        * 
+        * @return the LazyMethodGen representing the ajc$ clinit
+        */
        public LazyMethodGen getAjcPreClinit() {
-               for (Iterator i = methodGens.iterator(); i.hasNext();) {
-                       LazyMethodGen gen = (LazyMethodGen) i.next();
-                       if (gen.getName().equals(NameMangler.AJC_PRE_CLINIT_NAME)) {
-                               return gen;
+               if (this.isInterface()) {
+                       throw new IllegalStateException();
+               }
+               for (LazyMethodGen methodGen : methodGens) {
+                       if (methodGen.getName().equals(NameMangler.AJC_PRE_CLINIT_NAME)) {
+                               return methodGen;
                        }
                }
-               LazyMethodGen ajcClinit = new LazyMethodGen(Modifier.STATIC, Type.VOID, NameMangler.AJC_PRE_CLINIT_NAME, new Type[0],
-                               NO_STRINGS, this);
-               ajcClinit.getBody().insert(InstructionConstants.RETURN);
-               methodGens.add(ajcClinit);
-
-               getStaticInitializer().getBody().insert(Utility.createInvoke(getFactory(), ajcClinit));
-               return ajcClinit;
+               LazyMethodGen ajcPreClinit = new LazyMethodGen(Modifier.PRIVATE | Modifier.STATIC, Type.VOID,
+                               NameMangler.AJC_PRE_CLINIT_NAME, Type.NO_ARGS, NO_STRINGS, this);
+               ajcPreClinit.getBody().insert(InstructionConstants.RETURN);
+               methodGens.add(ajcPreClinit);
+               getStaticInitializer().getBody().insert(Utility.createInvoke(fact, ajcPreClinit));
+               return ajcPreClinit;
        }
 
        // reflective thisJoinPoint support
@@ -1092,8 +1101,11 @@ public final class LazyClassGen {
                        il.append(InstructionFactory.PUSH(getConstantPool(), calculatedSerialVersionUID));
                        il.append(getFactory().createFieldAccess(getClassName(), "serialVersionUID", BasicType.LONG, Constants.PUTSTATIC));
                }
-
-               getStaticInitializer().getBody().insert(il);
+               if (this.isInterface()) { // Cannot sneak stuff into another static method in an interface
+                       getStaticInitializer().getBody().insert(il);
+               } else {
+                       getAjcPreClinit().getBody().insert(il);
+               }
        }
 
        private InstructionList initializeAllTjps() {
index a8a7209bb715a307b7c4f14f8696119990e6f96c..3eab53d7bdf1a92e8f1a6a7d34966fd72e96d90a 100644 (file)
@@ -1735,4 +1735,8 @@ public final class LazyMethodGen implements Traceable {
                return toShortString();
        }
 
+       public ConstantPool getConstantPool() {
+               return enclosingClass.getConstantPool();
+       }
+
 }