org.jetbrains.jet.compiler.NamespaceComparator.java Source code

Java tutorial

Introduction

Here is the source code for org.jetbrains.jet.compiler.NamespaceComparator.java

Source

/*
 * Copyright 2010-2012 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jetbrains.jet.compiler;

import com.google.common.io.Files;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.codegen.PropertyCodegen;
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
import org.jetbrains.jet.lang.descriptors.ClassKind;
import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor;
import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
import org.jetbrains.jet.lang.descriptors.Modality;
import org.jetbrains.jet.lang.descriptors.ModuleDescriptor;
import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor;
import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.jet.lang.resolve.java.JavaDescriptorResolver;
import org.jetbrains.jet.lang.resolve.java.JavaNamespaceDescriptor;
import org.jetbrains.jet.lang.resolve.scopes.JetScope;
import org.jetbrains.jet.lang.resolve.scopes.receivers.ExtensionReceiver;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.TypeProjection;
import org.jetbrains.jet.lang.types.Variance;
import org.junit.Assert;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author Stepan Koltsov
 */
class NamespaceComparator {

    private final boolean includeObject;

    private NamespaceComparator(boolean includeObject) {
        this.includeObject = includeObject;
    }

    public static void compareNamespaces(@NotNull NamespaceDescriptor nsa, @NotNull NamespaceDescriptor nsb,
            boolean includeObject, @NotNull File txtFile) {
        String serialized = new NamespaceComparator(includeObject).doCompareNamespaces(nsa, nsb);
        try {
            for (;;) {
                String expected = Files.toString(txtFile, Charset.forName("utf-8")).replace("\r\n", "\n");

                if (expected.contains("kick me")) {
                    // for developer
                    System.err.println("generating " + txtFile);
                    Files.write(serialized, txtFile, Charset.forName("utf-8"));
                    continue;
                }

                // compare with hardcopy: make sure nothing is lost in output
                Assert.assertEquals(expected, serialized);
                break;
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String doCompareNamespaces(@NotNull NamespaceDescriptor nsa, @NotNull NamespaceDescriptor nsb) {
        StringBuilder sb = new StringBuilder();
        Assert.assertEquals(nsa.getName(), nsb.getName());

        sb.append("namespace " + nsa.getName() + "\n\n");

        Assert.assertTrue(!nsa.getMemberScope().getAllDescriptors().isEmpty());

        Set<String> classifierNames = new HashSet<String>();
        Set<String> propertyNames = new HashSet<String>();
        Set<String> functionNames = new HashSet<String>();

        for (DeclarationDescriptor ad : nsa.getMemberScope().getAllDescriptors()) {
            if (ad instanceof ClassifierDescriptor) {
                classifierNames.add(ad.getName());
            } else if (ad instanceof PropertyDescriptor) {
                propertyNames.add(ad.getName());
            } else if (ad instanceof FunctionDescriptor) {
                functionNames.add(ad.getName());
            } else {
                throw new AssertionError("unknown member: " + ad);
            }
        }

        for (String name : classifierNames) {
            ClassifierDescriptor ca = nsa.getMemberScope().getClassifier(name);
            ClassifierDescriptor cb = nsb.getMemberScope().getClassifier(name);
            Assert.assertTrue(ca != null);
            Assert.assertTrue(cb != null);
            compareClassifiers(ca, cb, sb);
        }

        for (String name : propertyNames) {
            Set<VariableDescriptor> pa = nsa.getMemberScope().getProperties(name);
            Set<VariableDescriptor> pb = nsb.getMemberScope().getProperties(name);
            compareDeclarationSets(pa, pb, sb);

            Assert.assertTrue(nsb.getMemberScope().getFunctions(PropertyCodegen.getterName(name)).isEmpty());
            Assert.assertTrue(nsb.getMemberScope().getFunctions(PropertyCodegen.setterName(name)).isEmpty());
        }

        for (String name : functionNames) {
            Set<FunctionDescriptor> fa = nsa.getMemberScope().getFunctions(name);
            Set<FunctionDescriptor> fb = nsb.getMemberScope().getFunctions(name);
            compareDeclarationSets(fa, fb, sb);
        }

        return sb.toString();
    }

    private static void compareDeclarationSets(Set<? extends DeclarationDescriptor> a,
            Set<? extends DeclarationDescriptor> b, @NotNull StringBuilder sb) {
        String at = serializedDeclarationSets(a);
        String bt = serializedDeclarationSets(b);
        Assert.assertEquals(at, bt);
        sb.append(at);
    }

    private static String serializedDeclarationSets(Collection<? extends DeclarationDescriptor> ds) {
        List<String> strings = new ArrayList<String>();
        for (DeclarationDescriptor d : ds) {
            StringBuilder sb = new StringBuilder();
            new Serializer(sb).serialize(d);
            strings.add(sb.toString());
        }

        Collections.sort(strings, new MemberComparator());

        StringBuilder r = new StringBuilder();
        for (String string : strings) {
            r.append(string);
            r.append("\n");
        }
        return r.toString();
    }

    /**
     * This comparator only affects test output, you can drop it if you don't want to understand it.
     */
    private static class MemberComparator implements Comparator<String> {

        @NotNull
        private String normalize(String s) {
            return s.replaceFirst(
                    "^ *(private|final|abstract|open|override|fun|val|var|/\\*.*?\\*/|((?!<init>)<.*?>)| )*", "")
                    + s;
        }

        @Override
        public int compare(@NotNull String a, @NotNull String b) {
            return normalize(a).compareTo(normalize(b));
        }
    }

    private void compareClassifiers(@NotNull ClassifierDescriptor a, @NotNull ClassifierDescriptor b,
            @NotNull StringBuilder sb) {
        StringBuilder sba = new StringBuilder();
        StringBuilder sbb = new StringBuilder();

        new FullContentSerialier(sba).serialize((ClassDescriptor) a);
        new FullContentSerialier(sbb).serialize((ClassDescriptor) b);

        String as = sba.toString();
        String bs = sbb.toString();

        Assert.assertEquals(as, bs);
        sb.append(as);
    }

    private static class Serializer {

        protected final StringBuilder sb;

        public Serializer(StringBuilder sb) {
            this.sb = sb;
        }

        public void serialize(ClassKind kind) {
            switch (kind) {
            case CLASS:
                sb.append("class");
                break;
            case TRAIT:
                sb.append("trait");
                break;
            case OBJECT:
                sb.append("object");
                break;
            case ANNOTATION_CLASS:
                sb.append("annotation class");
                break;
            default:
                throw new IllegalStateException("unknown class kind: " + kind);
            }
        }

        private static Object invoke(Method method, Object thiz, Object... args) {
            try {
                return method.invoke(thiz, args);
            } catch (Exception e) {
                throw new RuntimeException("failed to invoke " + method + ": " + e, e);
            }
        }

        public void serialize(FunctionDescriptor fun) {
            serialize(fun.getModality());
            sb.append(" ");

            if (!fun.getAnnotations().isEmpty()) {
                new Serializer(sb).serializeSeparated(fun.getAnnotations(), " ");
                sb.append(" ");
            }

            if (!fun.getOverriddenDescriptors().isEmpty()) {
                sb.append("override /*" + fun.getOverriddenDescriptors().size() + "*/ ");
            }

            if (fun instanceof ConstructorDescriptor) {
                sb.append("/*constructor*/ ");
            }
            sb.append("fun ");
            if (!fun.getTypeParameters().isEmpty()) {
                sb.append("<");
                new Serializer(sb).serializeCommaSeparated(fun.getTypeParameters());
                sb.append(">");
            }

            if (fun.getReceiverParameter().exists()) {
                new Serializer(sb).serialize(fun.getReceiverParameter());
                sb.append(".");
            }

            sb.append(fun.getName());
            sb.append("(");
            new ValueParameterSerializer(sb).serializeCommaSeparated(fun.getValueParameters());
            sb.append("): ");
            new Serializer(sb).serialize(fun.getReturnType());
        }

        public void serialize(ExtensionReceiver extensionReceiver) {
            serialize(extensionReceiver.getType());
        }

        public void serialize(PropertyDescriptor prop) {
            serialize(prop.getModality());
            sb.append(" ");

            if (!prop.getAnnotations().isEmpty()) {
                new Serializer(sb).serializeSeparated(prop.getAnnotations(), " ");
                sb.append(" ");
            }

            if (prop.isVar()) {
                sb.append("var ");
            } else {
                sb.append("val ");
            }
            if (!prop.getTypeParameters().isEmpty()) {
                sb.append(" <");
                new Serializer(sb).serializeCommaSeparated(prop.getTypeParameters());
                sb.append("> ");
            }
            if (prop.getReceiverParameter().exists()) {
                // TODO: print only name for type parameter
                new Serializer(sb).serialize(prop.getReceiverParameter().getType());
                sb.append(".");
            }
            sb.append(prop.getName());
            sb.append(": ");
            new Serializer(sb).serialize(prop.getType());
        }

        public void serialize(ValueParameterDescriptor valueParameter) {
            sb.append("/*");
            sb.append(valueParameter.getIndex());
            sb.append("*/ ");
            if (valueParameter.getVarargElementType() != null) {
                sb.append("vararg ");
            }
            sb.append(valueParameter.getName());
            sb.append(": ");
            if (valueParameter.getVarargElementType() != null) {
                serialize(valueParameter.getVarargElementType());
            } else {
                serialize(valueParameter.getType());
            }
            if (valueParameter.hasDefaultValue()) {
                sb.append(" = ?");
            }
        }

        public void serialize(Variance variance) {
            if (variance == Variance.INVARIANT) {

            } else {
                sb.append(variance);
                sb.append(' ');
            }
        }

        public void serialize(Modality modality) {
            sb.append(modality.name().toLowerCase());
        }

        public void serialize(@NotNull JetType type) {
            serialize(type.getConstructor().getDeclarationDescriptor());
            if (!type.getArguments().isEmpty()) {
                sb.append("<");
                boolean first = true;
                for (TypeProjection proj : type.getArguments()) {
                    if (!first) {
                        sb.append(", ");
                    }
                    serialize(proj.getProjectionKind());
                    serialize(proj.getType());
                    first = false;
                }
                sb.append(">");
            }
            if (type.isNullable()) {
                sb.append("?");
            }
        }

        public void serialize(AnnotationDescriptor annotation) {
            serialize(annotation.getType());
            sb.append("(");
            serializeCommaSeparated(annotation.getValueArguments());
            sb.append(")");
        }

        public void serializeCommaSeparated(List<?> list) {
            serializeSeparated(list, ", ");
        }

        public void serializeSeparated(List<?> list, String sep) {
            boolean first = true;
            for (Object o : list) {
                if (!first) {
                    sb.append(sep);
                }
                serialize(o);
                first = false;
            }
        }

        private Method getMethodToSerialize(Object o) {
            if (o == null) {
                throw new IllegalStateException("won't serialize null");
            }

            // TODO: cache
            for (Method method : this.getClass().getMethods()) {
                if (!method.getName().equals("serialize")) {
                    continue;
                }
                if (method.getParameterTypes().length != 1) {
                    continue;
                }
                if (method.getParameterTypes()[0].equals(Object.class)) {
                    continue;
                }
                if (method.getParameterTypes()[0].isInstance(o)) {
                    method.setAccessible(true);
                    return method;
                }
            }
            throw new IllegalStateException("don't know how to serialize " + o + " (of " + o.getClass() + ")");
        }

        public void serialize(Object o) {
            Method method = getMethodToSerialize(o);
            invoke(method, this, o);
        }

        public void serialize(String s) {
            sb.append(s);
        }

        public void serialize(ModuleDescriptor module) {
            // nop
        }

        public void serialize(ClassDescriptor clazz) {
            new NamespacePrefixSerializer(sb).serialize(clazz.getContainingDeclaration());
            sb.append(clazz.getName());
        }

        public void serialize(NamespaceDescriptor ns) {
            if (isRootNs(ns)) {
                return;
            }
            if (ns.getContainingDeclaration() != null) {
                new NamespacePrefixSerializer(sb).serialize(ns.getContainingDeclaration());
            }
            sb.append(ns.getName());
        }

        public void serialize(TypeParameterDescriptor param) {
            sb.append("/*");
            sb.append(param.getIndex());
            if (param.isReified()) {
                sb.append(",r");
            }
            sb.append("*/ ");
            serialize(param.getVariance());
            sb.append(param.getName());
            if (!param.getUpperBounds().isEmpty()) {
                sb.append(" : ");
                List<String> list = new ArrayList<String>();
                for (JetType upper : param.getUpperBounds()) {
                    StringBuilder sb = new StringBuilder();
                    new ValueParameterSerializer(sb).serialize(upper);
                    list.add(sb.toString());
                }
                Collections.sort(list);
                serializeSeparated(list, " & "); // TODO: use where
            }
            // TODO: lower bounds
        }

    }

    private static boolean isRootNs(DeclarationDescriptor ns) {
        // upyachka
        return ns instanceof JavaNamespaceDescriptor && JavaDescriptorResolver.JAVA_ROOT.equals(ns.getName());
    }

    private static class NamespacePrefixSerializer extends Serializer {

        public NamespacePrefixSerializer(StringBuilder sb) {
            super(sb);
        }

        @Override
        public void serialize(NamespaceDescriptor ns) {
            super.serialize(ns);
            if (isRootNs(ns)) {
                return;
            }
            sb.append(".");
        }

        @Override
        public void serialize(ClassDescriptor clazz) {
            super.serialize(clazz);
            sb.append(".");
        }
    }

    private static class ValueParameterSerializer extends Serializer {

        public ValueParameterSerializer(StringBuilder sb) {
            super(sb);
        }

        @Override
        public void serialize(TypeParameterDescriptor param) {
            sb.append(param.getName());
        }
    }

    private class FullContentSerialier extends Serializer {
        private FullContentSerialier(StringBuilder sb) {
            super(sb);
        }

        public void serialize(ClassDescriptor klass) {

            if (!klass.getAnnotations().isEmpty()) {
                new Serializer(sb).serializeSeparated(klass.getAnnotations(), " ");
                sb.append(" ");
            }
            serialize(klass.getModality());
            sb.append(" ");

            serialize(klass.getKind());
            sb.append(" ");

            new Serializer(sb).serialize(klass);

            if (!klass.getTypeConstructor().getParameters().isEmpty()) {
                sb.append("<");
                serializeCommaSeparated(klass.getTypeConstructor().getParameters());
                sb.append(">");
            }

            if (!klass.getTypeConstructor().getSupertypes().isEmpty()) {
                sb.append(" : ");
                new Serializer(sb).serializeCommaSeparated(
                        new ArrayList<JetType>(klass.getTypeConstructor().getSupertypes()));
            }

            sb.append(" {\n");

            List<TypeProjection> typeArguments = new ArrayList<TypeProjection>();
            for (TypeParameterDescriptor param : klass.getTypeConstructor().getParameters()) {
                typeArguments.add(new TypeProjection(Variance.INVARIANT, param.getDefaultType()));
            }

            List<String> memberStrings = new ArrayList<String>();

            for (ConstructorDescriptor constructor : klass.getConstructors()) {
                StringBuilder constructorSb = new StringBuilder();
                new Serializer(constructorSb).serialize(constructor);
                memberStrings.add(constructorSb.toString());
            }

            JetScope memberScope = klass.getMemberScope(typeArguments);
            for (DeclarationDescriptor member : memberScope.getAllDescriptors()) {
                if (!includeObject) {
                    if (member.getName()
                            .matches("equals|hashCode|finalize|wait|notify(All)?|toString|clone|getClass")) {
                        continue;
                    }
                }
                StringBuilder memberSb = new StringBuilder();
                new FullContentSerialier(memberSb).serialize(member);
                memberStrings.add(memberSb.toString());
            }

            Collections.sort(memberStrings, new MemberComparator());

            for (String memberString : memberStrings) {
                sb.append(indent(memberString));
            }

            if (klass.getClassObjectDescriptor() != null) {
                StringBuilder sbForClassObject = new StringBuilder();
                new FullContentSerialier(sbForClassObject).serialize(klass.getClassObjectDescriptor());
                sb.append(indent(sbForClassObject.toString()));
            }

            sb.append("}\n");
        }
    }

    private static String indent(String string) {
        try {
            StringBuilder r = new StringBuilder();
            BufferedReader reader = new BufferedReader(new StringReader(string));
            for (;;) {
                String line = reader.readLine();
                if (line == null) {
                    break;
                }
                r.append("    ");
                r.append(line);
                r.append("\n");
            }
            return r.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}