Added tracing support to Protobuf Editor.

Change-Id: Ie4be4fb412b3bf7e9d77d74beecc91a255286960
diff --git a/com.google.eclipse.protobuf.ui/.options b/com.google.eclipse.protobuf.ui/.options
new file mode 100644
index 0000000..e05b026
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/.options
@@ -0,0 +1,7 @@
+# Debugging options for the com.google.eclipse.protobuf plugin.
+
+# Turns on debugging for the com.google.eclipse.protobuf plugin.
+com.google.eclipse.protobuf.ui/debug=false
+
+# Turns on debugging for com.google.eclipse.protobuf.scoping.
+com.google.eclipse.protobuf.ui/scoping=false
diff --git a/com.google.eclipse.protobuf.ui/META-INF/MANIFEST.MF b/com.google.eclipse.protobuf.ui/META-INF/MANIFEST.MF
index 09ab499..096c09d 100644
--- a/com.google.eclipse.protobuf.ui/META-INF/MANIFEST.MF
+++ b/com.google.eclipse.protobuf.ui/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name

 Bundle-Vendor: %Bundle-Vendor

 Bundle-Version: 2.2.1.qualifier

-Bundle-Activator: com.google.eclipse.protobuf.ui.internal.ProtobufActivator

+Bundle-Activator: com.google.eclipse.protobuf.ui.internal.Activator

 Bundle-SymbolicName: com.google.eclipse.protobuf.ui; singleton:=true

 Bundle-ActivationPolicy: lazy

 Require-Bundle: com.google.eclipse.protobuf,

diff --git a/com.google.eclipse.protobuf.ui/plugin.xml b/com.google.eclipse.protobuf.ui/plugin.xml
index 3570fe6..4f55fd7 100644
--- a/com.google.eclipse.protobuf.ui/plugin.xml
+++ b/com.google.eclipse.protobuf.ui/plugin.xml
@@ -278,4 +278,15 @@
     <extension point="org.eclipse.xtext.ui.shared.overridingGuiceModule">
     <module class="com.google.eclipse.protobuf.ui.SharedModuleOverrides"/>
   </extension>
+  <extension
+         point="org.eclipse.ui.trace.traceComponents">
+      <component
+            id="com.google.eclipse.protobuf.ui.trace"
+            label="%trace.component.label">
+         <bundle
+               consumed="false"
+               name="com.google.eclipse.protobuf.ui">
+         </bundle>
+      </component>
+   </extension>
 </plugin>
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/internal/Activator.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/internal/Activator.java
new file mode 100644
index 0000000..21da6ee
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/internal/Activator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2016 Google Inc.
+ *
+ * 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
+ */
+package com.google.eclipse.protobuf.ui.internal;
+
+import java.util.Hashtable;
+
+import org.eclipse.osgi.service.debug.DebugOptions;
+import org.eclipse.osgi.service.debug.DebugOptionsListener;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import com.google.eclipse.protobuf.util.Tracer;
+
+/**
+ * The activator class controls the plug-in life cycle.
+ *
+ * @author atrookey@google.com (Alexander Rookey)
+ */
+public class Activator extends ProtobufActivator {
+  private ServiceRegistration<DebugOptionsListener> debugOptionsListener;
+
+  @Override
+  public void start(BundleContext bundleContext) throws Exception {
+    Hashtable<String, String> props = new Hashtable<>(4);
+    props.put(DebugOptions.LISTENER_SYMBOLICNAME, "com.google.eclipse.protobuf.ui");
+    debugOptionsListener =
+        bundleContext.registerService(
+            DebugOptionsListener.class, Tracer.RESOURCES_DEBUG_OPTIONS_LISTENER, props);
+    super.start(bundleContext);
+  }
+
+  @Override
+  public void stop(BundleContext bundleContext) throws Exception {
+    try {
+      debugOptionsListener.unregister();
+    } finally {
+      super.stop(bundleContext);
+    }
+  }
+}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java
index e9d8556..3b82c45 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java
@@ -1,14 +1,16 @@
 /*
- * Copyright (c) 2011 Google Inc.
+ * Copyright (c) 2016 Google Inc.
  *
- * 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
- *
+ * 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
  */
 package com.google.eclipse.protobuf.scoping;
 
 import static com.google.eclipse.protobuf.scoping.OptionType.typeOf;
+import static com.google.eclipse.protobuf.util.Tracer.DEBUG_SCOPING;
+import static com.google.eclipse.protobuf.validation.ProtobufResourceValidator.getScopeProviderTimingCollector;
 import static java.util.Collections.emptySet;
 
 import com.google.eclipse.protobuf.model.util.MessageFields;
@@ -62,9 +64,11 @@
  *
  * @author alruiz@google.com (Alex Ruiz)
  *
- * @see <a href="http://www.eclipse.org/Xtext/documentation/latest/xtext.html#scoping">Xtext Scoping</a>
+ * @see <a href="http://www.eclipse.org/Xtext/documentation/latest/xtext.html#scoping">Xtext
+ * Scoping</a>
  */
-public class ProtobufScopeProvider extends AbstractDeclarativeScopeProvider implements ScopeProvider {
+public class ProtobufScopeProvider extends AbstractDeclarativeScopeProvider
+    implements ScopeProvider {
   private static final boolean DO_NOT_IGNORE_CASE = false;
 
   @Inject private ComplexTypeFinderStrategy complexTypeFinderDelegate;
@@ -83,6 +87,18 @@
   @Inject private NativeOptionDescriptions nativeOptionDescriptions;
   @Inject private Options options;
 
+  @Override
+  public IScope getScope(EObject context, EReference reference) {
+    if (DEBUG_SCOPING) {
+      getScopeProviderTimingCollector().startTimer();
+    }
+    IScope scope = super.getScope(context, reference);
+    if (DEBUG_SCOPING) {
+      getScopeProviderTimingCollector().stopTimer();
+    }
+    return scope;
+  }
+
   @SuppressWarnings("unused")
   public IScope scope_ComplexTypeLink_target(ComplexTypeLink link, EReference r) {
     EObject c = link.eContainer();
@@ -100,7 +116,8 @@
     return createEmptyScope();
   }
 
-  @Override public Collection<IEObjectDescription> potentialComplexTypesFor(MessageField field) {
+  @Override
+  public Collection<IEObjectDescription> potentialComplexTypesFor(MessageField field) {
     return modelElementFinder.find(field, complexTypeFinderDelegate, ComplexType.class);
   }
 
@@ -112,7 +129,8 @@
     return createScope(extensibleTypes);
   }
 
-  @Override public Collection<IEObjectDescription> potentialExtensibleTypesFor(TypeExtension extension) {
+  @Override
+  public Collection<IEObjectDescription> potentialExtensibleTypesFor(TypeExtension extension) {
     Protobuf root = modelObjects.rootOf(extension);
     return modelElementFinder.find(root, complexTypeFinderDelegate, ExtensibleType.class);
   }
@@ -124,12 +142,14 @@
     return createScope(messages);
   }
 
-  @Override public Collection<IEObjectDescription> potentialMessagesFor(Rpc rpc) {
+  @Override
+  public Collection<IEObjectDescription> potentialMessagesFor(Rpc rpc) {
     Protobuf root = modelObjects.rootOf(rpc);
     return allMessages(root);
   }
 
-  @Override public Collection<IEObjectDescription> potentialMessagesFor(Stream stream) {
+  @Override
+  public Collection<IEObjectDescription> potentialMessagesFor(Stream stream) {
     Protobuf root = modelObjects.rootOf(stream);
     return allMessages(root);
   }
@@ -184,9 +204,10 @@
         EObject container = c.eContainer();
         if (container instanceof Group) {
           OptionType optionType = OptionType.findOptionTypeForLevelOf(container.eContainer());
-          return createScope(optionType != null 
-              ? modelElementFinder.find(option, customOptionFinderDelegate, optionType)
-              : Collections.<IEObjectDescription>emptySet());
+          return createScope(
+              optionType != null
+                  ? modelElementFinder.find(option, customOptionFinderDelegate, optionType)
+                  : Collections.<IEObjectDescription>emptySet());
         }
       }
 
@@ -196,7 +217,8 @@
     return createScope(descriptions);
   }
 
-  @Override public Collection<IEObjectDescription> potentialSourcesFor(AbstractCustomOption option) {
+  @Override
+  public Collection<IEObjectDescription> potentialSourcesFor(AbstractCustomOption option) {
     OptionType optionType = typeOf((AbstractOption) option);
     Collection<IEObjectDescription> descriptions = emptySet();
     if (optionType != null) {
@@ -225,11 +247,13 @@
     return emptySet();
   }
 
-  @Override public Collection<IEObjectDescription> potentialMessageFieldsFor(AbstractCustomOption option) {
+  @Override
+  public Collection<IEObjectDescription> potentialMessageFieldsFor(AbstractCustomOption option) {
     return customOptionFieldFinder.findOptionFields(option, messageFieldFinderDelegate);
   }
 
-  @Override public Collection<IEObjectDescription> potentialExtensionFieldsFor(AbstractCustomOption option) {
+  @Override
+  public Collection<IEObjectDescription> potentialExtensionFieldsFor(AbstractCustomOption option) {
     return customOptionFieldFinder.findOptionFields(option, extensionFieldFinderDelegate);
   }
 
@@ -260,12 +284,15 @@
     return null;
   }
 
-  @Override public Collection<IEObjectDescription> potentialNormalFieldNames(ComplexValue value) {
+  @Override
+  public Collection<IEObjectDescription> potentialNormalFieldNames(ComplexValue value) {
     return customOptionFieldNameFinder.findFieldNamesSources(value, normalFieldNameFinderDelegate);
   }
 
-  @Override public Collection<IEObjectDescription> potentialExtensionFieldNames(ComplexValue value) {
-    return customOptionFieldNameFinder.findFieldNamesSources(value, extensionFieldNameFinderDelegate);
+  @Override
+  public Collection<IEObjectDescription> potentialExtensionFieldNames(ComplexValue value) {
+    return customOptionFieldNameFinder.findFieldNamesSources(
+        value, extensionFieldNameFinderDelegate);
   }
 
   private static IScope createEmptyScope() {
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/TimingCollector.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/TimingCollector.java
new file mode 100644
index 0000000..3f79477
--- /dev/null
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/TimingCollector.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2016 Google Inc.
+ *
+ * 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
+ */
+package com.google.eclipse.protobuf.util;
+
+public class TimingCollector {
+  private long invocationCount;
+  private long sum;
+  private long time;
+
+  public double getAverageInMilliseconds() {
+    return sum * 1.e-6 / invocationCount;
+  }
+
+  public long getInvocationCount() {
+    return invocationCount;
+  }
+
+  public void startTimer() {
+    time = System.nanoTime();
+  }
+
+  public void stopTimer() {
+    sum += System.nanoTime() - time;
+    invocationCount++;
+  }
+
+  public void clear() {
+    invocationCount = 0;
+    sum = 0;
+  }
+
+  @Override
+  public String toString() {
+    return String.format(
+        "Invocation Count: %1$d Mean Duration: %2$fms",
+        getInvocationCount(),
+        getAverageInMilliseconds());
+  }
+}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Tracer.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Tracer.java
new file mode 100644
index 0000000..474b1bd
--- /dev/null
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Tracer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2016 Google Inc.
+ *
+ * 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
+ */
+package com.google.eclipse.protobuf.util;
+
+import org.eclipse.osgi.service.debug.DebugOptions;
+import org.eclipse.osgi.service.debug.DebugOptionsListener;
+import org.eclipse.osgi.service.debug.DebugTrace;
+
+/**
+ * The debugging related arguments for the protobuf editor.
+ *
+ * @author atrookey@google.com (Alexander Rookey)
+ */
+public class Tracer {
+  public static DebugTrace trace;
+  public static boolean DEBUG_SCOPING = false;
+  public static final String TRACE_PREFIX = "[Google Protobuf Editor] ";
+
+  public static final DebugOptionsListener RESOURCES_DEBUG_OPTIONS_LISTENER =
+      new DebugOptionsListener() {
+        @Override
+        public void optionsChanged(DebugOptions options) {
+          if (trace == null) {
+            trace = options.newDebugTrace("com.google.eclipse.protobuf.ui");
+          }
+          boolean debug = options.getBooleanOption("com.google.eclipse.protobuf.ui/debug", false);
+          DEBUG_SCOPING =
+              debug && options.getBooleanOption("com.google.eclipse.protobuf.ui/scoping", false);
+        }
+      };
+
+  /**
+   * Prints a trace message to standard output.
+   */
+  public static void trace(String message) {
+    System.out.println(TRACE_PREFIX + message);
+  }
+}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufResourceValidator.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufResourceValidator.java
index a33d0ec..969e2ba 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufResourceValidator.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufResourceValidator.java
@@ -1,9 +1,9 @@
 /*
- * Copyright (c) 2011 Google Inc.
+ * Copyright (c) 2016 Google Inc.
  *
- * 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
- *
+ * 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
  */
 package com.google.eclipse.protobuf.validation;
@@ -15,6 +15,8 @@
 import static org.eclipse.xtext.validation.CheckMode.KEY;
 import static org.eclipse.xtext.validation.CheckType.FAST;
 import static org.eclipse.xtext.validation.impl.ConcreteSyntaxEValidator.DISABLE_CONCRETE_SYNTAX_EVALIDATOR;
+import static com.google.eclipse.protobuf.util.Tracer.DEBUG_SCOPING;
+import static com.google.eclipse.protobuf.util.Tracer.trace;
 
 import static com.google.common.collect.Lists.newArrayListWithExpectedSize;
 import static com.google.common.collect.Maps.newHashMap;
@@ -38,6 +40,7 @@
 import org.eclipse.xtext.validation.ResourceValidatorImpl;
 
 import com.google.eclipse.protobuf.linking.ProtobufDiagnostic;
+import com.google.eclipse.protobuf.util.TimingCollector;
 
 /**
  * Adds support for converting scoping errors into warnings if non-proto2 files are imported.
@@ -46,14 +49,32 @@
  */
 public class ProtobufResourceValidator extends ResourceValidatorImpl {
   private static final Logger log = Logger.getLogger(ProtobufResourceValidator.class);
+  private static final ThreadLocal<TimingCollector> scopeProviderTimingCollector =
+      new ThreadLocal<TimingCollector>() {
+        @Override
+        public TimingCollector initialValue() {
+          return new TimingCollector();
+        }
+      };
 
-  @Override public List<Issue> validate(Resource resource, CheckMode mode, CancelIndicator indicator) {
+  @Override
+  public List<Issue> validate(Resource resource, CheckMode mode, CancelIndicator indicator) {
     CancelIndicator monitor = indicator == null ? CancelIndicator.NullImpl : indicator;
+    getScopeProviderTimingCollector().clear();
     resolveProxies(resource, monitor);
+    if (DEBUG_SCOPING) {
+      trace("Debugging AbstractDeclarativeScopeProvider.getScope() "
+           + getScopeProviderTimingCollector().toString());
+    }
+    return handleIssues(resource, mode, monitor);
+  }
+
+  private List<Issue> handleIssues(Resource resource, CheckMode mode, CancelIndicator monitor) {
     if (monitor.isCanceled()) {
       return null;
     }
-    List<Issue> result = newArrayListWithExpectedSize(resource.getErrors().size() + resource.getWarnings().size());
+    List<Issue> result =
+        newArrayListWithExpectedSize(resource.getErrors().size() + resource.getWarnings().size());
     try {
       IAcceptor<Issue> acceptor = createAcceptor(result);
       Status status = delegateValidationToDiagnostician(resource, mode, monitor, acceptor);
@@ -76,14 +97,15 @@
     return result;
   }
 
-  private Status delegateValidationToDiagnostician(Resource resource, CheckMode mode,
-      CancelIndicator monitor, IAcceptor<Issue> acceptor) {
+  private Status delegateValidationToDiagnostician(
+      Resource resource, CheckMode mode, CancelIndicator monitor, IAcceptor<Issue> acceptor) {
     Status hasNonProto2Import = Status.OK;
     for (EObject element : resource.getContents()) {
       if (monitor.isCanceled()) {
         return Status.CANCELED;
       }
-      Diagnostic diagnostic = getDiagnostician().validate(element, validationOptions(resource, mode, monitor));
+      Diagnostic diagnostic =
+          getDiagnostician().validate(element, validationOptions(resource, mode, monitor));
       if (convertIssuesToMarkers(acceptor, diagnostic) == Status.PROTO1_IMPORTS_FOUND) {
         hasNonProto2Import = Status.PROTO1_IMPORTS_FOUND;
       }
@@ -91,7 +113,8 @@
     return hasNonProto2Import;
   }
 
-  private Map<Object, Object> validationOptions(Resource resource, CheckMode mode, CancelIndicator monitor) {
+  private Map<Object, Object> validationOptions(
+      Resource resource, CheckMode mode, CancelIndicator monitor) {
     Map<Object, Object> options = newHashMap();
     options.put(KEY, mode);
     options.put(CANCEL_INDICATOR, monitor);
@@ -118,7 +141,10 @@
     return hasNonProto2Import;
   }
 
-  private Status createErrors(Resource resource, boolean proto1ImportsFound, IAcceptor<Issue> acceptor,
+  private Status createErrors(
+      Resource resource,
+      boolean proto1ImportsFound,
+      IAcceptor<Issue> acceptor,
       CancelIndicator monitor) {
     for (Resource.Diagnostic error : resource.getErrors()) {
       if (monitor.isCanceled()) {
@@ -154,7 +180,8 @@
     return false;
   }
 
-  private Status createWarnings(Resource resource, IAcceptor<Issue> acceptor, CancelIndicator monitor) {
+  private Status createWarnings(
+      Resource resource, IAcceptor<Issue> acceptor, CancelIndicator monitor) {
     for (Resource.Diagnostic warning : resource.getWarnings()) {
       if (monitor.isCanceled()) {
         return Status.CANCELED;
@@ -165,7 +192,9 @@
   }
 
   private static enum Status {
-    OK, CANCELED, PROTO1_IMPORTS_FOUND;
+    OK,
+    CANCELED,
+    PROTO1_IMPORTS_FOUND;
 
     boolean hasProto1Imports() {
       return (this == PROTO1_IMPORTS_FOUND);
@@ -175,4 +204,8 @@
       return (this == CANCELED);
     }
   }
-}
\ No newline at end of file
+
+  public static TimingCollector getScopeProviderTimingCollector() {
+    return scopeProviderTimingCollector.get();
+  }
+}