Bug 485495 - [Formatter] does not insert space before semicolon at the end of the statement
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java
index d7cdd89..7c9a542 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
  * 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
@@ -13145,4 +13145,56 @@
 		assertTrue(false);
 	}
 }
+/**
+ * https://bugs.eclipse.org/485495 - [Formatter] does not insert space before semicolon at the end of the statement
+ */
+public void testBug485495() {
+	this.formatterPrefs.insert_space_before_semicolon = true;
+	String source =
+		"package test ;\n" + 
+		"\n" + 
+		"import java.util.ArrayList ;\n" + 
+		"\n" + 
+		"public class Test {\n" + 
+		"\n" + 
+		"	interface I {\n" + 
+		"		void method() ;\n" + 
+		"	}\n" + 
+		"\n" + 
+		"	ArrayList<String> e = null ;\n" + 
+		"	int i ;\n" + 
+		"\n" + 
+		"	void foo() {\n" + 
+		"		int i = 0 ;\n" + 
+		"		String s ;\n" + 
+		"		if (i > 0)\n" + 
+		"			return ;\n" + 
+		"		for (int j = 0; j < 5; j++) {\n" + 
+		"			Object o ;\n" + 
+		"			while (i < 0)\n" + 
+		"				o = new Object() {\n" + 
+		"					int f ;\n" + 
+		"\n" + 
+		"					void bar() {\n" + 
+		"						if (f > 0)\n" + 
+		"							f = 5 ;\n" + 
+		"						else\n" + 
+		"							f = 16 ;\n" + 
+		"						try {\n" + 
+		"							f = 14 ;\n" + 
+		"						} catch (Exception e) {\n" + 
+		"							bar() ;\n" + 
+		"						}\n" + 
+		"					}\n" + 
+		"				} ;\n" + 
+		"			while (i < 0)\n" + 
+		"				switch (i) {\n" + 
+		"				case 4:\n" + 
+		"					foo() ;\n" + 
+		"				}\n" + 
+		"		}\n" + 
+		"	}\n" + 
+		"}";
+	formatSource(source);
+}
 }
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/SpacePreparator.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/SpacePreparator.java
index df0be65..5afb6fb 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/SpacePreparator.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/SpacePreparator.java
@@ -1,5 +1,5 @@
 /*******************************************************************************

- * Copyright (c) 2014, 2015 Mateusz Matela and others.

+ * Copyright (c) 2014, 2016 Mateusz Matela and others.

  * 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

@@ -46,6 +46,7 @@
 import org.eclipse.jdt.core.dom.FieldDeclaration;

 import org.eclipse.jdt.core.dom.ForStatement;

 import org.eclipse.jdt.core.dom.IfStatement;

+import org.eclipse.jdt.core.dom.ImportDeclaration;

 import org.eclipse.jdt.core.dom.InfixExpression;

 import org.eclipse.jdt.core.dom.InstanceofExpression;

 import org.eclipse.jdt.core.dom.IntersectionType;

@@ -56,6 +57,7 @@
 import org.eclipse.jdt.core.dom.MethodDeclaration;

 import org.eclipse.jdt.core.dom.MethodInvocation;

 import org.eclipse.jdt.core.dom.NormalAnnotation;

+import org.eclipse.jdt.core.dom.PackageDeclaration;

 import org.eclipse.jdt.core.dom.ParameterizedType;

 import org.eclipse.jdt.core.dom.ParenthesizedExpression;

 import org.eclipse.jdt.core.dom.PostfixExpression;

@@ -102,6 +104,18 @@
 	}

 

 	@Override

+	public boolean visit(PackageDeclaration node) {

+		handleSemicolon(node);

+		return true;

+	}

+

+	@Override

+	public boolean visit(ImportDeclaration node) {

+		handleSemicolon(node);

+		return true;

+	}

+

+	@Override

 	public boolean visit(TypeDeclaration node) {

 		if (node.getName().getStartPosition() == -1)

 			return true; // this is a fake type created by parsing in class body mode

@@ -234,6 +248,8 @@
 			handleTokenBefore(typeParameters.get(0), TokenNameLESS, true, false);

 			handleTokenAfter(typeParameters.get(typeParameters.size() - 1), TokenNameGREATER, false, true);

 		}

+

+		handleSemicolon(node);

 		return true;

 	}

 

@@ -255,6 +271,7 @@
 		handleToken((ASTNode) node.fragments().get(0), TokenNameIdentifier, true, false);

 		handleCommas(node.fragments(), this.options.insert_space_before_comma_in_multiple_field_declarations,

 				this.options.insert_space_after_comma_in_multiple_field_declarations);

+		handleSemicolon(node);

 		return true;

 	}

 

@@ -299,6 +316,7 @@
 				this.options.insert_space_before_closing_paren_in_switch, false);

 		handleTokenAfter(node.getExpression(), TokenNameLBRACE,

 				this.options.insert_space_before_opening_brace_in_switch, false);

+		handleSemicolon(node.statements());

 		return true;

 	}

 

@@ -509,6 +527,8 @@
 

 	@Override

 	public boolean visit(Block node) {

+		handleSemicolon(node.statements());

+

 		ASTNode parent = node.getParent();

 		if (parent.getLength() == 0)

 			return true; // this is a fake block created by parsing in statements mode

@@ -539,6 +559,8 @@
 			handleToken(thenStatement, TokenNameLBRACE, false, true);

 			this.tm.lastTokenIn(node, TokenNameRBRACE).spaceBefore();

 		}

+

+		handleSemicolon(thenStatement);

 		return true;

 	}

 

@@ -999,6 +1021,21 @@
 		return false;

 	}

 

+	private void handleSemicolon(ASTNode node) {

+		if (this.options.insert_space_before_semicolon) {

+			Token lastToken = this.tm.lastTokenIn(node, -1);

+			if (lastToken.tokenType == TokenNameSEMICOLON)

+				lastToken.spaceBefore();

+		}

+	}

+

+	private void handleSemicolon(List<ASTNode> nodes) {

+		if (this.options.insert_space_before_semicolon) {

+			for (ASTNode node : nodes)

+				handleSemicolon(node);

+		}

+	}

+

 	public void finishUp() {

 		this.tm.traverse(0, new TokenTraverser() {

 			boolean isPreviousJIDP = false;