Example usage for org.objectweb.asm Opcodes ASM5

List of usage examples for org.objectweb.asm Opcodes ASM5

Introduction

In this page you can find the example usage for org.objectweb.asm Opcodes ASM5.

Prototype

int ASM5

To view the source code for org.objectweb.asm Opcodes ASM5.

Click Source Link

Usage

From source file:com.bit.learning.java.asm.UDFByteCodeVerifier.java

License:Apache License

public Set<String> verify(byte[] bytes) {
    final Set<String> errors = new TreeSet<String>(); // it's a TreeSet for unit tests
    ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5) {
        public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
            errors.add("field declared: " + name);
            return null;
        }//from ww w  . ja v a2 s.c  om

        public MethodVisitor visitMethod(int access, String name, String desc, String signature,
                String[] exceptions) {

            if (true) {
                return new EvaluateVisitor(errors);
            }

            if ("<init>".equals(name)) {
                if (Opcodes.ACC_PUBLIC != access)
                    errors.add("constructor not public");
                // allowed constructor - JavaUDF(TypeCodec returnCodec, TypeCodec[] argCodecs)
                return new ConstructorVisitor(errors);
            }
            if ("<init>".equals(name) && CTOR_SIG.equals("()V")) {
                if (Opcodes.ACC_PUBLIC != access)
                    errors.add("constructor not public");
                // allowed constructor - JavaUDF(TypeCodec returnCodec, TypeCodec[] argCodecs)
                return new ConstructorVisitor(errors);
            }

            if ("evaluate".equals(name)) {
                if (Opcodes.ACC_PRIVATE == access) {
                    //This should be right, because user can use private evaluate method
                    errors.add("evaluate is private");
                }

                return new EvaluateVisitor(errors);
            }

            if ("executeImpl".equals(name) && "(ILjava/util/List;)Ljava/nio/ByteBuffer;".equals(desc)) {
                if (Opcodes.ACC_PROTECTED != access)
                    errors.add("executeImpl not protected");
                // the executeImpl method - ByteBuffer executeImpl(int protocolVersion, List<ByteBuffer> params)
                return new ExecuteImplVisitor(errors);
            }
            if ("<clinit>".equals(name)) {
                errors.add("static initializer declared");
            } else {
                //                    errors.add("not allowed method declared: " + name + desc);
                //                    return new ExecuteImplVisitor(errors);
            }
            return null;
        }

        public void visit(int version, int access, String name, String signature, String superName,
                String[] interfaces) {
            //                if (!JAVA_UDF_NAME.equals(superName)) {
            //                    errors.add("class does not extend " + JavaUDF.class.getName());
            //                }
            //                if (access != (Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER)) {
            //                    errors.add("class not public final");
            //                }
            super.visit(version, access, name, signature, superName, interfaces);
        }

        public void visitInnerClass(String name, String outerName, String innerName, int access) {
            errors.add("class declared as inner class");
            super.visitInnerClass(name, outerName, innerName, access);
        }
    };

    ClassReader classReader = new ClassReader(bytes);
    classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);

    return errors;
}

From source file:com.blade.kit.AsmKit.java

License:Apache License

/**
 *
 * <p>/* www  .j  av  a  2s  .  c o  m*/
 * ????
 * </p>
 *
 * @param m
 * @return
 */
public static String[] getMethodParamNames(final Method m) throws IOException {
    final String[] paramNames = new String[m.getParameterTypes().length];
    final String n = m.getDeclaringClass().getName();
    ClassReader cr = null;
    try {
        cr = new ClassReader(n);
    } catch (IOException e) {
        return null;
    }
    cr.accept(new ClassVisitor(Opcodes.ASM5) {
        @Override
        public MethodVisitor visitMethod(final int access, final String name, final String desc,
                final String signature, final String[] exceptions) {
            final Type[] args = Type.getArgumentTypes(desc);
            // ?????
            if (!name.equals(m.getName()) || !sameType(args, m.getParameterTypes())) {
                return super.visitMethod(access, name, desc, signature, exceptions);
            }
            MethodVisitor v = super.visitMethod(access, name, desc, signature, exceptions);
            return new MethodVisitor(Opcodes.ASM5, v) {
                @Override
                public void visitLocalVariable(String name, String desc, String signature, Label start,
                        Label end, int index) {
                    int i = index - 1;
                    // ???
                    // ???"this"???
                    if (Modifier.isStatic(m.getModifiers())) {
                        i = index;
                    }
                    if (i >= 0 && i < paramNames.length) {
                        paramNames[i] = name;
                    }
                    super.visitLocalVariable(name, desc, signature, start, end, index);
                }
            };
        }
    }, 0);
    return paramNames;
}

From source file:com.centimia.orm.jaqu.ext.asm.JaquClassAdapter.java

License:Open Source License

@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
    if (isEntityAnnotationPresent || isMappedSupperClass) {
        // collect the fields that are relation by rule. (Collection type fields....)
        if (desc.indexOf("java/util/List") != -1 || desc.indexOf("java/util/Set") != -1
                || desc.indexOf("java/util/Collection") != -1)
            relationFields.add(name.toLowerCase());
    }/* www.j  a va 2  s  .c  om*/
    return new JaquFieldVisitor(Opcodes.ASM5, super.visitField(access, name, desc, signature, value),
            name.toLowerCase(), relationFields, lazyLoadFields);
}

From source file:com.codename1.tools.ikvm.Parser.java

/**
 * Parses an InputStream containing a class.
 * @param input The input stream with a class.
 * @return If a transformation occurred, the bytes for the changed class will be returned.  Otherwise null will be returned.
 * @throws Exception //from   w w  w.  j  a v  a 2 s . c  o m
 */
public static byte[] parse(InputStream input, ClassLoader classLoader) throws Exception {
    ClassReader r = new ClassReader(input);
    Parser p = new Parser();
    //ClassWriter w = new ClassWriter(r, 0);
    ClassNode classNode = new ClassNode();
    //p.classNode = classNode;

    r.accept(classNode, 0);
    //r.accept(p, ClassReader.EXPAND_FRAMES)

    List<MethodNode> methodsToAdd = new ArrayList<MethodNode>();
    int methodNum = 0;
    for (Object o : classNode.methods) {
        methodNum++;
        MethodNode methodNode = (MethodNode) o;
        boolean synchronizedMethod = (methodNode.access & Opcodes.ACC_SYNCHRONIZED) == Opcodes.ACC_SYNCHRONIZED;
        if (synchronizedMethod) {
            // Check for a try statement
            final boolean[] tryCatchFound = new boolean[1];
            //System.out.println("Found sync method "+methodNode.name+". Checking for try blocks");
            methodNode.accept(new MethodVisitor(Opcodes.ASM5) {

                @Override
                public void visitTryCatchBlock(Label label, Label label1, Label label2, String string) {
                    tryCatchFound[0] = true;
                }

            });
            if (!tryCatchFound[0]) {
                continue;
            }

            //System.out.println("Instructions: "+Arrays.toString(methodNode.instructions.toArray()));

            System.out.println("Transforming method " + methodNode.name + " of class " + classNode.name);
            MethodDescriptor md = new MethodDescriptor(methodNode.access, methodNode.name, methodNode.desc);
            //methodNode.access = methodNode.access & ~Opcodes.ACC_SYNCHRONIZED;
            String privateMethodName = (md.constructor ? "___cn1init__" : methodNode.name) + "___cn1sync"
                    + (methodNum);
            MethodNode syncMethod = new MethodNode(methodNode.access, methodNode.name, methodNode.desc,
                    methodNode.signature,
                    (String[]) methodNode.exceptions.toArray(new String[methodNode.exceptions.size()]));

            methodNode.name = privateMethodName;
            methodNode.access = (methodNode.access | Opcodes.ACC_PRIVATE) & ~Opcodes.ACC_PUBLIC
                    & ~Opcodes.ACC_PROTECTED & ~Opcodes.ACC_SYNCHRONIZED;
            LabelNode startLabel = new LabelNode();
            syncMethod.instructions.add(startLabel);
            LabelNode endLabel = new LabelNode();

            int argIndex = 0;
            if (!md.staticMethod) {
                //System.out.println(methodNode.name + " is not static");
                syncMethod.localVariables.add(new LocalVariableNode("arg" + (argIndex),
                        "L" + classNode.name + ";", null, startLabel, endLabel, argIndex));
                syncMethod.instructions.add(new VarInsnNode(Opcodes.ALOAD, argIndex++));

            }

            for (ByteCodeMethodArg arg : md.arguments) {
                char typeChar = arg.type;
                if (arg.dim > 0) {
                    typeChar = 'L';
                }
                if (arg.desc == null || arg.desc.isEmpty()) {
                    throw new RuntimeException(
                            "Invalid arg description for arg " + argIndex + " of method " + methodNode.name);
                }
                syncMethod.localVariables.add(new LocalVariableNode("arg" + (argIndex), arg.desc, arg.desc,
                        startLabel, endLabel, argIndex));

                switch (typeChar) {
                case 'L':
                    syncMethod.instructions.add(new VarInsnNode(Opcodes.ALOAD, argIndex++));
                    //syncMethod.localVariables.add(new LocalVariableNode("arg"+(argIndex-1), arg.desc, null, startLabel, endLabel, argIndex-1));
                    break;
                case 'S':
                case 'I':
                case 'B':
                case 'Z':
                case 'C':
                    syncMethod.instructions.add(new VarInsnNode(Opcodes.ILOAD, argIndex++));
                    break;
                case 'J':
                    syncMethod.instructions.add(new VarInsnNode(Opcodes.LLOAD, argIndex++));
                    argIndex++; // arg index increments 2 for double size args
                    break;
                case 'F':
                    syncMethod.instructions.add(new VarInsnNode(Opcodes.FLOAD, argIndex++));
                    break;
                case 'D':
                    syncMethod.instructions.add(new VarInsnNode(Opcodes.DLOAD, argIndex++));
                    argIndex++;// arg index increments 2 for double size args
                    break;
                default:
                    throw new IllegalArgumentException("Unsupported argument type " + arg.type);
                }
            }

            if (md.staticMethod) {
                syncMethod.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, classNode.name,
                        privateMethodName, methodNode.desc));
            } else {
                syncMethod.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, classNode.name,
                        privateMethodName, methodNode.desc));
            }

            if (md.returnType != null) {
                char typeChar = md.returnType.type;
                if (md.returnType.dim > 0) {
                    typeChar = 'L';
                }
                switch (typeChar) {
                case 'L':
                    syncMethod.instructions.add(new InsnNode(Opcodes.ARETURN));
                    break;
                case 'S':
                case 'I':
                case 'B':
                case 'Z':
                case 'C':
                    syncMethod.instructions.add(new InsnNode(Opcodes.IRETURN));
                    break;
                case 'J':
                    syncMethod.instructions.add(new InsnNode(Opcodes.LRETURN));
                    break;
                case 'F':
                    syncMethod.instructions.add(new InsnNode(Opcodes.FRETURN));
                    break;
                case 'D':
                    syncMethod.instructions.add(new InsnNode(Opcodes.DRETURN));
                    break;
                case 'V':
                    syncMethod.instructions.add(new InsnNode(Opcodes.RETURN));
                    break;
                default:
                    throw new IllegalArgumentException("Unsupported argument type " + md.returnType.type);
                }
            } else {
                syncMethod.instructions.add(new InsnNode(Opcodes.DRETURN));
            }

            syncMethod.instructions.add(endLabel);

            methodsToAdd.add(syncMethod);

        }
    }
    if (!methodsToAdd.isEmpty()) {
        changed = true;
        System.out
                .println("Transforming " + methodsToAdd.size() + " synchronized methods in " + classNode.name);
        classNode.methods.addAll(methodsToAdd);
        ClassWriter w = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        classNode.accept(w);
        byte[] out = w.toByteArray();
        if (verify) {
            verify(out, classLoader);
        }
        return out;
    } else {
        ClassWriter w = new ClassWriter(0);
        classNode.accept(w);
        byte[] out = w.toByteArray();
        return out;
    }

}

From source file:com.codename1.tools.translator.Parser.java

License:Open Source License

public Parser() {
    super(Opcodes.ASM5);
}

From source file:com.costlowcorp.eriktools.ArchiveWalkerTest.java

public void testWalkWar() throws IOException {
    final String war = "C:\\apps\\thing.ear";
    final Path path = Paths.get(war);
    final LongAdder fileCount = new LongAdder();
    final Set<String> ownedPackages = new TreeSet<>();
    final StringBuilder sb = new StringBuilder();
    try (InputStream in = Files.newInputStream(path); ZipInputStream zin = new ZipInputStream(in)) {

        final ArchiveWalkerRecipient eachFile = (t, entry, u) -> {
            fileCount.increment();//w  w  w  .j  av a  2 s. c  om
            final String lastName = t.get(t.size() - 1);
            if (lastName.endsWith(".class")) {
                sb.append("Class File: " + String.join("->", t) + "\n");
                final ClassFileMetaVisitor v = new ClassFileMetaVisitor(Opcodes.ASM5, null);
                try {
                    final byte[] bytes = ArchiveWalker.currentEntry(u);
                    final ClassReader reader = new ClassReader(bytes);
                    reader.accept(v, ClassReader.SKIP_CODE);
                    final String packageName = v.getName().contains("/")
                            ? v.getName().substring(0, v.getName().lastIndexOf('/'))
                            : "EmptyPackage";
                    sb.append(" JAVA " + v.getJava() + " PKG " + packageName + "\n");
                } catch (IOException ex) {
                    Logger.getLogger(ArchiveWalkerTest.class.getName()).log(Level.SEVERE, null, ex);
                }
                //System.out.println();
            }
        };
        final ArchiveWalker walker = new ArchiveWalker(war, zin, eachFile);

        walker.walk();
    }
    System.out.println(sb.toString());
    System.out.println("There are " + fileCount.longValue() + " files.");
}

From source file:com.costlowcorp.eriktools.jardetails.JarDetailsController.java

private void updateCodeSigning(JarFile jar) throws IOException {
    final Set<CodeSigner> codeSigners = new HashSet<>();
    final Set<Certificate> certificates = new HashSet<>();
    final Map<String, Integer> fileCount = new HashMap<>();
    final Manifest jarManifest = jar.getManifest();

    final SortedSet<String> unsignedFiles = new TreeSet<>(String::compareToIgnoreCase);
    final SortedSet<String> signedFiles = new TreeSet<>(String::compareToIgnoreCase);
    final SortedSet<String> signedInvalidFiles = new TreeSet<>(String::compareToIgnoreCase);
    final Set<String> javaVersions = new HashSet<>(5);
    final Set<String> jarFilesInJar = new TreeSet<>();
    final Map<String, AtomicInteger> packageCount = new TreeMap<>();
    final ObservableList<PackageCount> packageData = FXCollections.observableArrayList();

    jar.stream().filter(entry -> !entry.isDirectory()).forEach(entry -> {
        final String name = entry.getName();
        final String extension = ErikUtils.getExtension(name);
        if ("jar".equalsIgnoreCase(extension)) {
            jarFilesInJar.add(name);//  w w w  .  j a  v a  2 s . c  o m
        }
        final BiConsumer<byte[], Integer> readFileIfNeeded;
        final Consumer<Void> countProperly;
        if ("class".equals(extension)) {
            //@TODO parse with ASM
            final ByteArrayOutputStream out = new ByteArrayOutputStream();
            readFileIfNeeded = (bytes, length) -> readClassBytes(out, bytes, length);

            countProperly = (vf) -> {
                final ClassReader reader = new ClassReader(out.toByteArray());
                final ClassFileMetaVisitor v = new ClassFileMetaVisitor(Opcodes.ASM5, null);
                reader.accept(v, ClassReader.SKIP_CODE);
                fileCount.put(v.getLanguage(), fileCount.getOrDefault(v.getLanguage(), 0) + 1);
                javaVersions.add(v.getJava());

                final String packageName = v.getName().contains("/")
                        ? v.getName().substring(0, v.getName().lastIndexOf('/'))
                        : "";
                if (packageCount.containsKey(packageName)) {
                    packageCount.get(packageName).getAndIncrement();
                } else {
                    packageCount.put(packageName, new AtomicInteger(1));
                }
            };

        } else {
            countProperly = v -> fileCount.put(extension, fileCount.getOrDefault(extension, 0) + 1);
            readFileIfNeeded = (bytes, length) -> Objects.nonNull(bytes);
        }
        final byte[] bytes = new byte[2048];
        try (InputStream in = jar.getInputStream(entry)) {
            for (int length = in.read(bytes); length > 0; length = in.read(bytes)) {
                for (MessageDigest digest : digests.values()) {
                    digest.update(bytes, 0, length);
                }
                readFileIfNeeded.accept(bytes, length);
            }

        } catch (IOException ex) {
            Logger.getLogger(JarDetailsController.class.getName()).log(Level.SEVERE, "Unable to read " + entry,
                    ex);
        }
        countProperly.accept(null);

        if (entry.getCodeSigners() != null) {
            codeSigners.addAll(Arrays.asList(entry.getCodeSigners()));
        }
        if (entry.getCertificates() != null) {
            certificates.addAll(Arrays.asList(entry.getCertificates()));
        }

        final Attributes attributes = jarManifest.getAttributes(name);
        checkSignatures(name, attributes, unsignedFiles, signedFiles, signedInvalidFiles);

        digests.values().forEach(MessageDigest::reset);
    });
    packageCount.entrySet().stream().map(pCount -> new PackageCount(pCount.getKey(), pCount.getValue().get(),
            checkSeal(pCount.getKey(), jarManifest))).forEach(packageData::add);
    Platform.runLater(() -> {
        if (codeSigners.isEmpty() || certificates.isEmpty()) {
            updateSignatureNo();
        } else {
            updateSignatureYes(codeSigners, certificates, unsignedFiles, signedFiles, signedInvalidFiles);
        }
        fileCount.forEach(
                (ext, count) -> languageChart.getData().add(new PieChart.Data(ext, count.doubleValue())));
        javaVersions.stream().max(ClassFileUtils::latestVersion).ifPresent(ver -> runsOn.setText(ver));
        if (jarFilesInJar.isEmpty()) {
            embeddedJars.getChildren().add(new Label("None"));
        } else {
            jarFilesInJar.stream().map(j -> new Label(j)).forEach(l -> embeddedJars.getChildren().add(l));
        }
        packageTable.setItems(packageData);
    });
}

From source file:com.costlowcorp.eriktools.wardetails.BasicBytecodeScan.java

@Override
protected Void call() throws Exception {
    final LongAdder classCount = new LongAdder();
    final Double upMaxClasses = maxClasses;
    urlRoot = new TreeItem<>(new IdentifiedURL("/", "", "", ""));
    urlRoot.setExpanded(true);/* w ww  .ja  v  a  2s .c o m*/

    try (InputStream in = Files.newInputStream(path); ZipInputStream zin = new ZipInputStream(in)) {
        final ArchiveWalkerRecipient eachFile = (t, entry, u) -> {
            classCount.increment();
            final double currentClass = classCount.doubleValue();
            final String message = String.format("Class %1.0f of %1.0f", currentClass, upMaxClasses);
            updateMessage(message);
            updateProgress(currentClass, maxClasses);
            if (entry.getName().toLowerCase().endsWith(".class")) {

                try {
                    final byte[] bytes = ArchiveWalker.currentEntry(u);
                    final URLIdentificationVisitor urlIdentificationVisitor = new URLIdentificationVisitor(
                            Opcodes.ASM5, null);
                    final ClassFileMetaVisitor v = new ClassFileMetaVisitor(Opcodes.ASM5,
                            urlIdentificationVisitor);
                    final ClassReader reader = new ClassReader(bytes);
                    reader.accept(v, ClassReader.SKIP_CODE);
                    final String pkg = v.getPackage();
                    final boolean foundJar = t.stream()
                            .filter(archive -> archive.toLowerCase().contains(".jar")).findAny().isPresent();
                    if (!foundJar && !"".equals(pkg)) {
                        ownedPackages.add(pkg);
                    }
                    final List<String> names = new ArrayList<>(t.size());
                    names.addAll(t);
                    names.set(names.size() - 1, pkg);
                    final String key = makeName(names);
                    if (items.containsKey(key)) {
                        final TreeItem<ArchiveOwnershipEntry> item = items.get(key);
                        final FileTime lastModified = entry.getLastModifiedTime() == null
                                ? FileTime.fromMillis(0)
                                : entry.getLastModifiedTime();
                        item.getValue().setWhenMade(new Date(lastModified.toMillis()));
                    } else {
                        final FileTime lastModified = entry.getLastModifiedTime() == null
                                ? FileTime.fromMillis(0)
                                : entry.getLastModifiedTime();
                        final TreeItem<ArchiveOwnershipEntry> item = nest(items, names, lastModified);
                        item.getValue().setWhenMade(new Date(lastModified.toMillis()));
                    }

                    if (!urlIdentificationVisitor.getIdentifiedURLs().isEmpty()) {
                        urlIdentificationVisitor.getIdentifiedURLs().stream().map(id -> new TreeItem<>(id))
                                .forEach(urlRoot.getChildren()::add);
                    }
                } catch (IOException ex) {
                    Logger.getLogger(BasicBytecodeScan.class.getName()).log(Level.SEVERE, null, ex);
                }

            }
        };
        final ArchiveWalker walker = new ArchiveWalker(path.toString(), zin, eachFile);
        walker.walk();
    } catch (Exception ex) {
        Logger.getLogger(WarDetailsController.class.getName()).log(Level.SEVERE, "The task failed", ex);
    }
    done();
    return null;
}

From source file:com.costlowcorp.eriktools.wardetails.CountFileIntrospectTypesTask.java

@Override
protected Void call() throws Exception {
    final DoubleAdder fileCount = new DoubleAdder();

    try (InputStream in = Files.newInputStream(path); ZipInputStream zin = new ZipInputStream(in)) {
        final Double upTotalFiles = totalFiles;
        final ArchiveWalkerRecipient eachFile = (t, entry, u) -> {
            fileCount.add(1.0);/* w w  w.  j  a  v  a  2  s. com*/
            final double currentClass = fileCount.doubleValue();
            //final String joinedFile = String.join("->", t);
            final String message = String.format("File %1.0f of %1.0f", currentClass, upTotalFiles);
            updateMessage(message);
            updateProgress(currentClass, totalFiles);
            final String justFilename = t.get(t.size() - 1);
            if (entry.isDirectory()) {
                return;
            }
            final String extension = ErikUtils.getExtension(justFilename);
            String key = "unknown";
            switch (extension) {
            case "class":
                try {
                    final byte[] bytes = ArchiveWalker.currentEntry(u);
                    final ClassReader reader = new ClassReader(bytes);
                    final ClassFileMetaVisitor v = new ClassFileMetaVisitor(Opcodes.ASM5, null);
                    reader.accept(v, ClassReader.SKIP_CODE);
                    key = v.getLanguage();
                } catch (IOException ex) {
                    Logger.getLogger(CountFileIntrospectTypesTask.class.getName()).log(Level.SEVERE,
                            "Unable to read entry " + String.join("->", t), ex);
                    key = "Invalid Java";
                }
                break;
            default:
                key = extension;
            }
            final double currentCount = chartData.getOrDefault(key, 0.0);
            chartData.put(key, currentCount + 1);
        };
        final ArchiveWalker walker = new ArchiveWalker(path.toString(), zin, eachFile);
        walker.walk();
    } catch (IOException ex) {
        Logger.getLogger(WarDetailsController.class.getName()).log(Level.SEVERE, "The task failed", ex);
    }

    return null;
}

From source file:com.costlowcorp.eriktools.wardetails.WarScanner.java

private void readClass(ZipEntry entry, ZipInputStream zis) throws IOException {
    final String entryName = entry.getName();
    final byte[] classBytes = readBytes(zis);
    final ClassReader reader = new ClassReader(classBytes);
    final ClassFileMetaVisitor v = new ClassFileMetaVisitor(Opcodes.ASM5, null);
    reader.accept(v, ClassReader.SKIP_FRAMES);
    fileCount.put(v.getLanguage(), fileCount.getOrDefault(v.getLanguage(), 0) + 1);
    if (v.getName().contains("/")) {
        final String currentPackage = v.getName().substring(0, v.getName().lastIndexOf('/'));
        packages.add(currentPackage);/*from ww  w  . j a  v a 2s. co m*/
    } else {
        //cannot own empty package
    }

}