/*
 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package java.lang.reflect;

import java.lang.annotation.Annotation;
import java.lang.annotation.AnnotationFormatError;
import java.lang.annotation.RetentionPolicy;
import java.lang.module.ModuleClassLoader;
import java.lang.module.ModuleId;
import java.lang.module.ModuleInfo;
import java.lang.module.ModuleNotPresentException;
import java.lang.module.Version;
import java.security.CodeSource;
import java.util.Map;
import java.util.LinkedHashMap;
import sun.reflect.annotation.AnnotationType;
import org.openjdk.jigsaw.Loader;

public final class Module
    implements AnnotatedElement
{

    private ModuleInfo moduleInfo;
    private ModuleClassLoader loader;
    private final CodeSource codeSource;

    /**
     * Package-private constructor used by ReflectAccess to enable
     * instantiation of these objects in Java code from the java.lang
     * package via sun.reflect.LangReflectAccess.
     */
    Module(ModuleInfo mi, ModuleClassLoader ld, CodeSource cs) {
        loader = ld;
        moduleInfo = mi;
        codeSource = cs;
    }

    public ModuleClassLoader getClassLoader() {
        return loader;
    }

    public ModuleInfo getModuleInfo() {
        return moduleInfo;
    }

    public CodeSource getCodeSource() {
        return codeSource;
    }

    // Convenience methods

    public ModuleId getModuleId() {
        return moduleInfo.id();
    }

    public String getName() {
        return moduleInfo.id().name();
    }

    public Version getVersion() {
        return moduleInfo.id().version();
    }


    //  -- AnnotatedElement methods --

    /**
     * {@inheritDoc}
     */
    public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return getAnnotation(annotationClass) != null;
    }

    /**
     * {@inheritDoc}
     */
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
        if (annotationClass == null)
            throw new NullPointerException();

        return (A) annotationsMap().get(annotationClass);
    }

    /**
     * {@inheritDoc}
     */
    public Annotation[] getAnnotations() {
        // no inherited annotations
        return getDeclaredAnnotations();
    }

    /**
     * {@inheritDoc}
     */
    public Annotation[] getDeclaredAnnotations() {
        return annotationsMap.values().toArray(new Annotation[0]);
    }

    private transient Map<Class<? extends Annotation>, Annotation> annotationsMap;
    // Returns the cached annotations
    private synchronized  Map<Class<? extends Annotation>, Annotation> annotationsMap() {
        if (annotationsMap != null)
            return annotationsMap;

        // module-info.class is not loaded in the VM as a Class object
        // we can't use sun.reflect.annotation.AnnotationParser here
        annotationsMap = new LinkedHashMap<Class<? extends Annotation>, Annotation>();
        for (Annotation a: sun.misc.SharedSecrets.
                               getJavaLangModuleAccess().getAnnotations(moduleInfo, this)) {
            Class<? extends Annotation> klass = a.annotationType();
            AnnotationType type = AnnotationType.getInstance(klass);
            if (type.retention() == RetentionPolicy.RUNTIME) {
                if (annotationsMap.put(klass, a) != null) {
                    throw new AnnotationFormatError(
                        "Duplicate annotation for class: "+klass+": " + a);
                }
            }
        }
        return annotationsMap;
    }

    // ## EHS

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getName());
        sb.append("(").append(moduleInfo.id()).append(")");
        return sb.toString();
    }

    /**
     * Tests if a module of the given module name 
     * has been resolved and linked with this module's context.
     *
     * @param mn a module's name
     * @return true if the module of the given name is present;
     *         false otherwise.
     */
    public boolean isModulePresent(String mn) {
        return ((Loader)loader).isModulePresent(mn);
    }

    /**
     * Checks if a module of the given module name 
     * has been resolved and linked with this module's context.
     *
     * @param mn a module's name
     * @throws ModuleNotPresentException if the module of the given name
     *         is not present in this module's context.
     */
    public void requireModulePresent(String mn) {
        if (!isModulePresent(mn)) {
            throw new ModuleNotPresentException("module " + mn + " not present");
        }
    }

}