groovy git commit: Refine groovydoc support

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

groovy git commit: Refine groovydoc support

Daniel.Sun
Repository: groovy
Updated Branches:
  refs/heads/master c7f5931a0 -> b364be085


Refine groovydoc support


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/b364be08
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/b364be08
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/b364be08

Branch: refs/heads/master
Commit: b364be0850833f14012bfdd1bd31f29a82c8e0cd
Parents: c7f5931
Author: sunlan <[hidden email]>
Authored: Sun Sep 10 11:37:28 2017 +0800
Committer: sunlan <[hidden email]>
Committed: Sun Sep 10 11:37:28 2017 +0800

----------------------------------------------------------------------
 src/main/org/codehaus/groovy/ast/ClassNode.java | 10 ++-
 src/main/org/codehaus/groovy/ast/FieldNode.java | 13 +++-
 .../org/codehaus/groovy/ast/MethodNode.java     |  9 ++-
 .../groovy/ast/groovydoc/Groovydoc.java         | 68 ++++++++++++++++++
 .../groovy/ast/groovydoc/GroovydocHolder.java   | 31 ++++++++
 .../groovy/ast/groovydoc/GroovydocTag.java      | 74 ++++++++++++++++++++
 .../groovy/parser/antlr4/GroovydocManager.java  |  9 ++-
 .../parser/antlr4/GroovyParserTest.groovy       | 56 +++++++--------
 .../antlr4/util/ASTComparatorCategory.groovy    |  2 +-
 9 files changed, 236 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/b364be08/src/main/org/codehaus/groovy/ast/ClassNode.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/ast/ClassNode.java b/src/main/org/codehaus/groovy/ast/ClassNode.java
index 283e86f..7e24de2 100644
--- a/src/main/org/codehaus/groovy/ast/ClassNode.java
+++ b/src/main/org/codehaus/groovy/ast/ClassNode.java
@@ -23,6 +23,8 @@ import org.codehaus.groovy.ast.expr.BinaryExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.FieldExpression;
 import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.groovydoc.Groovydoc;
+import org.codehaus.groovy.ast.groovydoc.GroovydocHolder;
 import org.codehaus.groovy.ast.stmt.BlockStatement;
 import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
@@ -103,7 +105,8 @@ import java.util.Set;
  *
  * @see org.codehaus.groovy.ast.ClassHelper
  */
-public class ClassNode extends AnnotatedNode implements Opcodes {
+public class ClassNode extends AnnotatedNode implements Opcodes, GroovydocHolder {
+
     private static class MapOfLists {
         private Map<Object, List<MethodNode>> map;
         public List<MethodNode> get(Object key) {
@@ -1493,4 +1496,9 @@ public class ClassNode extends AnnotatedNode implements Opcodes {
     public String getText() {
         return getName();
     }
+
+    @Override
+    public Groovydoc getGroovydoc() {
+        return this.<Groovydoc>getNodeMetaData(DOC_COMMENT);
+    }
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/b364be08/src/main/org/codehaus/groovy/ast/FieldNode.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/ast/FieldNode.java b/src/main/org/codehaus/groovy/ast/FieldNode.java
index c791346..987349b 100644
--- a/src/main/org/codehaus/groovy/ast/FieldNode.java
+++ b/src/main/org/codehaus/groovy/ast/FieldNode.java
@@ -18,17 +18,19 @@
  */
 package org.codehaus.groovy.ast;
 
-import java.lang.reflect.Field;
-
 import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.groovydoc.Groovydoc;
+import org.codehaus.groovy.ast.groovydoc.GroovydocHolder;
 import org.objectweb.asm.Opcodes;
 
+import java.lang.reflect.Field;
+
 /**
  * Represents a field (member variable)
  *
  * @author <a href="mailto:[hidden email]">James Strachan</a>
  */
-public class FieldNode extends AnnotatedNode implements Opcodes, Variable {
+public class FieldNode extends AnnotatedNode implements Opcodes, Variable, GroovydocHolder {
 
     private String name;
     private int modifiers;
@@ -190,4 +192,9 @@ public class FieldNode extends AnnotatedNode implements Opcodes, Variable {
         declaringClass.renameField(this.name, name);
         this.name = name;
     }
+
+    @Override
+    public Groovydoc getGroovydoc() {
+        return this.<Groovydoc>getNodeMetaData(DOC_COMMENT);
+    }
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/b364be08/src/main/org/codehaus/groovy/ast/MethodNode.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/ast/MethodNode.java b/src/main/org/codehaus/groovy/ast/MethodNode.java
index 7239a86..ea0db81 100644
--- a/src/main/org/codehaus/groovy/ast/MethodNode.java
+++ b/src/main/org/codehaus/groovy/ast/MethodNode.java
@@ -19,6 +19,8 @@
 package org.codehaus.groovy.ast;
 
 import org.apache.groovy.ast.tools.MethodNodeUtils;
+import org.codehaus.groovy.ast.groovydoc.Groovydoc;
+import org.codehaus.groovy.ast.groovydoc.GroovydocHolder;
 import org.codehaus.groovy.ast.stmt.BlockStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
 import org.objectweb.asm.Opcodes;
@@ -31,7 +33,7 @@ import java.util.List;
  * @author <a href="mailto:[hidden email]">James Strachan</a>
  * @author Hamlet D'Arcy
  */
-public class MethodNode extends AnnotatedNode implements Opcodes {
+public class MethodNode extends AnnotatedNode implements Opcodes, GroovydocHolder {
 
     public static final String SCRIPT_BODY_METHOD_KEY = "org.codehaus.groovy.ast.MethodNode.isScriptBody";
     private final String name;
@@ -269,4 +271,9 @@ public class MethodNode extends AnnotatedNode implements Opcodes {
         String parms = AstToTextHelper.getParametersText(parameters);
         return AstToTextHelper.getModifiersText(modifiers) + " " + retType + " " + name + "(" + parms + ") " + exceptionTypes + " { ... }";
     }
+
+    @Override
+    public Groovydoc getGroovydoc() {
+        return this.<Groovydoc>getNodeMetaData(DOC_COMMENT);
+    }
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/b364be08/src/main/org/codehaus/groovy/ast/groovydoc/Groovydoc.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/ast/groovydoc/Groovydoc.java b/src/main/org/codehaus/groovy/ast/groovydoc/Groovydoc.java
new file mode 100644
index 0000000..04aef38
--- /dev/null
+++ b/src/main/org/codehaus/groovy/ast/groovydoc/Groovydoc.java
@@ -0,0 +1,68 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.codehaus.groovy.ast.groovydoc;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents groovydoc
+ */
+public class Groovydoc {
+    private String content;
+    private List<GroovydocTag> tagList;
+    private GroovydocHolder groovydocHolder;
+
+    public Groovydoc(String content, GroovydocHolder groovydocHolder) {
+        this.content = content;
+        this.groovydocHolder = groovydocHolder;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public List<GroovydocTag> getTagList() {
+        throw new UnsupportedOperationException("[TODO]parsing tags will be a new features of the next releases");
+//        return tagList;
+    }
+
+    public GroovydocHolder getGroovydocHolder() {
+        return groovydocHolder;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Groovydoc groovydoc = (Groovydoc) o;
+        return Objects.equals(content, groovydoc.content) &&
+                Objects.equals(groovydocHolder, groovydoc.groovydocHolder);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(content, groovydocHolder);
+    }
+
+    @Override
+    public String toString() {
+        return this.content;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/b364be08/src/main/org/codehaus/groovy/ast/groovydoc/GroovydocHolder.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/ast/groovydoc/GroovydocHolder.java b/src/main/org/codehaus/groovy/ast/groovydoc/GroovydocHolder.java
new file mode 100644
index 0000000..e5585fe
--- /dev/null
+++ b/src/main/org/codehaus/groovy/ast/groovydoc/GroovydocHolder.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.codehaus.groovy.ast.groovydoc;
+
+/**
+ * Represents Groovydoc Holder
+ */
+public interface GroovydocHolder {
+    String DOC_COMMENT = "_DOC_COMMENT"; // keys for meta data
+    /**
+     * Get the groovydoc
+     * @return the groovydoc
+     */
+    Groovydoc getGroovydoc();
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/b364be08/src/main/org/codehaus/groovy/ast/groovydoc/GroovydocTag.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/ast/groovydoc/GroovydocTag.java b/src/main/org/codehaus/groovy/ast/groovydoc/GroovydocTag.java
new file mode 100644
index 0000000..54b2e2e
--- /dev/null
+++ b/src/main/org/codehaus/groovy/ast/groovydoc/GroovydocTag.java
@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.codehaus.groovy.ast.groovydoc;
+
+import java.util.Objects;
+
+/**
+ * TODO parse groovydoc to get tag content
+ */
+public class GroovydocTag {
+    private String name;
+    private String content;
+    private Groovydoc groovydoc;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Groovydoc getGroovydoc() {
+        return groovydoc;
+    }
+
+    public void setGroovydoc(Groovydoc groovydoc) {
+        this.groovydoc = groovydoc;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        GroovydocTag that = (GroovydocTag) o;
+        return Objects.equals(name, that.name) &&
+                Objects.equals(content, that.content) &&
+                Objects.equals(groovydoc, that.groovydoc);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, content, groovydoc);
+    }
+
+    @Override
+    public String toString() {
+        return content;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/b364be08/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/GroovydocManager.java
----------------------------------------------------------------------
diff --git a/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/GroovydocManager.java b/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/GroovydocManager.java
index a9e5e90..b3b58f2 100644
--- a/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/GroovydocManager.java
+++ b/subprojects/parser-antlr4/src/main/java/org/apache/groovy/parser/antlr4/GroovydocManager.java
@@ -28,6 +28,7 @@ import org.codehaus.groovy.ast.AnnotatedNode;
 import org.codehaus.groovy.ast.AnnotationNode;
 import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.groovydoc.GroovydocHolder;
 
 import java.util.List;
 
@@ -40,7 +41,7 @@ import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean;
  * 3) attach groovydoc to AST node as metadata
  */
 public class GroovydocManager {
-    public static final String DOC_COMMENT = "_DOC_COMMENT"; // keys for meta data
+    public static final String DOC_COMMENT = GroovydocHolder.DOC_COMMENT; // keys for meta data
     private static final String DOC_COMMENT_PREFIX = "/**";
     private static final String TRUE_STR = "true";
 
@@ -104,7 +105,11 @@ public class GroovydocManager {
             return;
         }
 
-        node.putNodeMetaData(DOC_COMMENT, docCommentNodeText);
+        if (!(node instanceof GroovydocHolder)) {
+            return;
+        }
+
+        node.putNodeMetaData(DOC_COMMENT, new org.codehaus.groovy.ast.groovydoc.Groovydoc(docCommentNodeText, (GroovydocHolder) node));
     }
 
     /*

http://git-wip-us.apache.org/repos/asf/groovy/blob/b364be08/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
----------------------------------------------------------------------
diff --git a/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy b/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
index 2d31097..84ff8fc 100644
--- a/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
+++ b/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
@@ -52,34 +52,34 @@ class GroovyParserTest extends GroovyTestCase {
         List<ClassNode> classes = new ArrayList<>(newAST.classes).sort { c1, c2 -> c1.name <=> c2.name };
         List<MethodNode> methods = new ArrayList<>(newAST.methods).sort { m1, m2 -> m1.name <=> m2.name };
 
-        assert classes[0].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '')            == '/** * test class Comments */'
-        assert classes[0].fields[0].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '')  == '/**     * test Comments.SOME_VAR     */'
-        assert classes[0].fields[1].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '')  == '/**     * test Comments.SOME_VAR2     */'
-        assert classes[0].fields[2].nodeMetaData[GroovydocManager.DOC_COMMENT] == null
-        assert classes[0].fields[3].nodeMetaData[GroovydocManager.DOC_COMMENT] == null
-        assert classes[0].declaredConstructors[0].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '') == '/**     * test Comments.constructor1     */'
-        assert classes[0].methods[0].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '') == '/**     * test Comments.m1     */'
-        assert classes[0].methods[1].nodeMetaData[GroovydocManager.DOC_COMMENT] == null
-        assert classes[0].methods[2].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '') == '/**     * test Comments.m3     */'
-
-        assert classes[1].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '')            == '/**     * test class InnerClazz     */'
-        assert classes[1].fields[0].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '')  == '/**         * test InnerClazz.SOME_VAR3         */'
-        assert classes[1].fields[1].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '')  == '/**         * test InnerClazz.SOME_VAR4         */'
-        assert classes[1].methods[0].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '') == '/**         * test Comments.m4         */'
-        assert classes[1].methods[1].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '') == '/**         * test Comments.m5         */'
-
-        assert classes[2].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '')            == '/**     * test class InnerEnum     */'
-        assert classes[2].fields[0].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '')  == '/**         * InnerEnum.NEW         */'
-        assert classes[2].fields[1].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '')  == '/**         * InnerEnum.OLD         */'
-
-        assert classes[3].nodeMetaData[GroovydocManager.DOC_COMMENT] == null
-
-        assert classes[4].fields[0].nodeMetaData[GroovydocManager.DOC_COMMENT] == null
-
-        assert classes[5].nodeMetaData[GroovydocManager.DOC_COMMENT] == null
-
-        assert methods[0].nodeMetaData[GroovydocManager.DOC_COMMENT].replaceAll(/\r?\n/, '') == '/** * test someScriptMethod1 */'
-        assert methods[1].nodeMetaData[GroovydocManager.DOC_COMMENT] == null
+        assert classes[0].groovydoc.content.replaceAll(/\r?\n/, '')            == '/** * test class Comments */'
+        assert classes[0].fields[0].groovydoc.content.replaceAll(/\r?\n/, '')  == '/**     * test Comments.SOME_VAR     */'
+        assert classes[0].fields[1].groovydoc.content.replaceAll(/\r?\n/, '')  == '/**     * test Comments.SOME_VAR2     */'
+        assert classes[0].fields[2].groovydoc == null
+        assert classes[0].fields[3].groovydoc == null
+        assert classes[0].declaredConstructors[0].groovydoc.content.replaceAll(/\r?\n/, '') == '/**     * test Comments.constructor1     */'
+        assert classes[0].methods[0].groovydoc.content.replaceAll(/\r?\n/, '') == '/**     * test Comments.m1     */'
+        assert classes[0].methods[1].groovydoc == null
+        assert classes[0].methods[2].groovydoc.content.replaceAll(/\r?\n/, '') == '/**     * test Comments.m3     */'
+
+        assert classes[1].groovydoc.content.replaceAll(/\r?\n/, '')            == '/**     * test class InnerClazz     */'
+        assert classes[1].fields[0].groovydoc.content.replaceAll(/\r?\n/, '')  == '/**         * test InnerClazz.SOME_VAR3         */'
+        assert classes[1].fields[1].groovydoc.content.replaceAll(/\r?\n/, '')  == '/**         * test InnerClazz.SOME_VAR4         */'
+        assert classes[1].methods[0].groovydoc.content.replaceAll(/\r?\n/, '') == '/**         * test Comments.m4         */'
+        assert classes[1].methods[1].groovydoc.content.replaceAll(/\r?\n/, '') == '/**         * test Comments.m5         */'
+
+        assert classes[2].groovydoc.content.replaceAll(/\r?\n/, '')            == '/**     * test class InnerEnum     */'
+        assert classes[2].fields[0].groovydoc.content.replaceAll(/\r?\n/, '')  == '/**         * InnerEnum.NEW         */'
+        assert classes[2].fields[1].groovydoc.content.replaceAll(/\r?\n/, '')  == '/**         * InnerEnum.OLD         */'
+
+        assert classes[3].groovydoc == null
+
+        assert classes[4].fields[0].groovydoc == null
+
+        assert classes[5].groovydoc == null
+
+        assert methods[0].groovydoc.content.replaceAll(/\r?\n/, '') == '/** * test someScriptMethod1 */'
+        assert methods[1].groovydoc == null
     }
 
     void "test groovy core - PackageDeclaration"() {

http://git-wip-us.apache.org/repos/asf/groovy/blob/b364be08/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/ASTComparatorCategory.groovy
----------------------------------------------------------------------
diff --git a/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/ASTComparatorCategory.groovy b/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/ASTComparatorCategory.groovy
index 47b9507..19123f9 100644
--- a/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/ASTComparatorCategory.groovy
+++ b/subprojects/parser-antlr4/src/test/groovy/org/apache/groovy/parser/antlr4/util/ASTComparatorCategory.groovy
@@ -190,7 +190,7 @@ class ASTComparatorCategory {
             }
 
 
-            !(name in ignore) && (name != 'nodeMetaData' && name != 'metaDataMap') && a."$name" != b."$name"
+            !(name in ignore) && (name != 'nodeMetaData' && name != 'metaDataMap' && name != 'groovydoc') && a."$name" != b."$name"
         }
 
         if (difference)