Browse Source

Adding format code

Sam Jaffe 8 years ago
parent
commit
c4f7103eed

+ 110 - 0
pom.xml

@@ -0,0 +1,110 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.leumasjaffe</groupId>
+  <artifactId>format</artifactId>
+  <version>0.1</version>
+  <packaging>jar</packaging>
+
+  <name>format</name>
+  <url>http://maven.apache.org</url>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.eclipse.m2e</groupId>
+          <artifactId>lifecycle-mapping</artifactId>
+          <version>1.0.0</version>
+          <configuration>
+            <lifecycleMappingMetadata>
+              <pluginExecutions>
+                <pluginExecution>
+                  <pluginExecutionFilter>
+                    <groupId>org.projectlombok</groupId>
+                    <artifactId>lombok-maven-plugin</artifactId>
+                    <versionRange>[1,)</versionRange>
+                    <goals>
+                      <goal>delombok</goal>
+                    </goals>
+                  </pluginExecutionFilter>
+                  <action>
+                    <ignore />
+                  </action>
+                </pluginExecution>
+              </pluginExecutions>
+            </lifecycleMappingMetadata>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+    <sourceDirectory>target/generated-sources/delombok</sourceDirectory>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.5.1</version>
+        <configuration>
+          <compilerVersion>1.8</compilerVersion>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.projectlombok</groupId>
+        <artifactId>lombok-maven-plugin</artifactId>
+        <version>1.16.18.0</version>
+        <executions>
+          <execution>
+            <id>delombok</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>delombok</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <addOutputDirectory>false</addOutputDirectory>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <version>2.5.1</version>
+        <executions>
+          <execution>
+            <id>copy-dependencies</id>
+            <phase>package</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>
+                ${project.build.directory}/dependency-jars/
+              </outputDirectory>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.projectlombok</groupId>
+      <artifactId>lombok</artifactId>
+      <version>1.16.8</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>

+ 18 - 0
src/main/lombok/org/leumasjaffe/format/CustomToString.java

@@ -0,0 +1,18 @@
+package org.leumasjaffe.format;
+
+import java.util.function.Function;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.experimental.FieldDefaults;
+
+@AllArgsConstructor
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class CustomToString<T> {
+	T object;
+	Function<T, String> toStringFunction;
+	
+	public String toString() {
+		return toStringFunction.apply(object);
+	}
+}

+ 27 - 0
src/main/lombok/org/leumasjaffe/format/Named.java

@@ -0,0 +1,27 @@
+package org.leumasjaffe.format;
+
+import java.util.Objects;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.experimental.FieldDefaults;
+
+@Data
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class Named {
+	String name;
+	Object value;
+	
+	public Named(String n, Object v) {
+		Objects.requireNonNull(n);
+		this.name = n;
+		this.value = v;
+		if (n.matches("\\{|\\}|%")) {
+			throw new IllegalArgumentException("Cannot use '{', '}', or '%' in named arguments!");
+		}
+	}
+	
+	public String toString() {
+		return String.valueOf(value);
+	}
+}

+ 145 - 0
src/main/lombok/org/leumasjaffe/format/StringFormatter.java

@@ -0,0 +1,145 @@
+package org.leumasjaffe.format;
+
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import lombok.AccessLevel;
+import lombok.experimental.FieldDefaults;
+
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class StringFormatter {
+	private static final class NameMap extends HashMap<String, Object> {
+		/**
+		 * 
+		 */
+		private static final long serialVersionUID = -2243574068000774442L;
+
+		NameMap(Object... args) {
+			for (Object arg : args) {
+				Objects.requireNonNull(arg);
+				if (arg instanceof Named) {
+					put(((Named) arg).getName(), arg);
+				}
+			}
+		}
+	}
+	
+	private static final class FmtStateMachine {
+		final StringBuilder str;
+		
+		String fmt;
+		Object[] args;
+		int currentIdx;
+		final NameMap named;
+		int lastCh, endPos = 0;
+
+		FmtStateMachine(String fmt, Object... args) {
+			this(fmt, 0, args, new NameMap(args), '\0');
+		}
+		
+		FmtStateMachine(String fmt, int idx, Object[] args, NameMap named, int lastCh) {
+			this.str = new StringBuilder(fmt.length());
+			this.fmt = fmt;
+			this.currentIdx = idx;
+			this.args = args;
+			this.named = named;
+			this.lastCh = lastCh;
+		}
+		
+		FmtStateMachine formatMain() {
+			int lpos = 0;
+			for (int pos = fmt.indexOf('{'); pos != -1 && hasMore(pos); lpos = pos+1, pos = fmt.indexOf('{', lpos)) {
+				str.append(fmt.substring(lpos, pos).replaceAll("}}", "}"));
+				int epos = fmt.indexOf('}', pos);
+
+				if (fmt.charAt(pos+1) == '{') { 
+					// Literal '{'
+					str.append('{');
+					epos = pos + 1;
+				} else if (epos == pos+1) {
+					// Unmarked '{}' -> get the next argument
+					str.append(getToken(null));
+				} else {
+					epos = formatToken(pos, epos);
+				}
+				pos = epos;
+			}
+			str.append(remaining(lpos).replaceAll("}}", "}"));
+			return this;
+		}
+
+		private String remaining(int lpos) {
+			this.endPos = fmt.indexOf(lastCh, lpos);
+			return lastCh == '\0' ? fmt.substring(lpos) : fmt.substring(lpos, endPos++);
+		}
+
+		private boolean hasMore(int pos) {
+			return lastCh == '\0' || pos < fmt.indexOf(lastCh);
+		}
+		
+		private int formatToken(int pos, int epos) {
+			final String token = fmt.substring(++pos, epos);
+			// If token contains a '.', use reflection?
+			// If token starts with '%', make it a c-format expression
+			if (token.indexOf('?') != -1) {
+				final int b1 = fmt.indexOf('?', pos)+1;
+				boolean _if = getConditionToken(token, b1-pos-1);
+				FmtStateMachine then = new FmtStateMachine(fmt.substring(b1), currentIdx, args, named, ':').formatMain();
+				FmtStateMachine els = new FmtStateMachine(fmt.substring(then.endPos+b1), then.currentIdx, args, named, '}').formatMain();
+				currentIdx = els.currentIdx;
+				epos = els.endPos+then.endPos+b1-1;
+				str.append(_if ? then : els);
+			} else {
+				str.append(getToken(token));
+			}
+			return epos;
+		}
+
+		private boolean getConditionToken(final String token, final int end) {
+			final String cond = token.substring(0, end);
+			Matcher matcher = Pattern.compile("^(\\d*|[a-zA-Z_]+)(>=?|<=?|!=|==?)(\\d+)$").matcher(cond);
+			if (matcher.matches()) {
+				final int cmp = ((Integer) getToken(matcher.group(1))).compareTo(Integer.valueOf(matcher.group(3)));
+				switch (matcher.group(2)) {
+				case "=" : return cmp == 0;
+				case "==": return cmp == 0;
+				case "!=": return cmp != 0;
+				case "<" : return cmp <  0;
+				case "<=": return cmp <= 0;
+				case ">" : return cmp >  0;
+				case ">=": return cmp >= 0;
+				default: throw new IllegalStateException();
+				}
+			} else {
+				return (Boolean) getToken(cond);
+			}
+		}
+
+		private Object getToken(final String token) {
+			if (token == null || token.isEmpty()) {
+				return args[currentIdx++];
+			} else if (Character.isDigit(token.charAt(0))) {
+				return args[Integer.parseInt(token)];
+			} else {
+				return named.get(token);
+			}
+		}
+		
+		public String toString() {
+			return str.toString();
+		}
+	}
+	
+	String fmt;
+	
+	public StringFormatter(String logFmtString) {
+		Objects.requireNonNull(logFmtString);
+		this.fmt = logFmtString;
+	}
+	
+	public String format(Object... args) {
+		return new FmtStateMachine(fmt, args).formatMain().toString();
+	}
+}

+ 43 - 0
src/main/lombok/org/leumasjaffe/format/StringHelper.java

@@ -0,0 +1,43 @@
+package org.leumasjaffe.format;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class StringHelper {
+	public String toString(Object o) {
+		if ( o == null ) { return ""; }
+		else { return o.toString(); }
+	}
+	
+	public String toString(float f) {
+		return Float.toString(f);
+	}
+	
+	public String toString(int i) {
+		return Integer.toString(i);
+	}
+	
+	public String toString(int i, int ignore, String elseValue) {
+		if ( i == ignore ) { return elseValue; }
+		else { return Integer.toString(i); }
+	}
+
+	public String toString(int i, int ignore) {
+		if ( i == ignore ) { return ""; }
+		else { return Integer.toString(i); }
+	}
+	
+	public String toSignedString(int i) {
+		if ( i > 0 ) { return "+" + i; }
+		else { return toString(i); }
+	}
+	
+	public String toSignedString(int i, int ignore) {
+		if ( i == ignore ) { return ""; }
+		else { return toSignedString(i); }
+	}
+	
+	public String format(String logFmtString, Object... args) {
+		return new StringFormatter(logFmtString).format(args);
+	}
+}

+ 34 - 0
src/test/java/org/leumasjaffe/format/StringFormatterTest.java

@@ -0,0 +1,34 @@
+package org.leumasjaffe.format;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class StringFormatterTest {
+
+	@Test
+	public void testFormatLiteral() {
+		assertEquals("{}", new StringFormatter("{{}}").format());
+	}
+	
+	@Test
+	public void testFormatNumbered() {
+		assertEquals("1,0", new StringFormatter("{1},{0}").format(0, 1));
+	}
+	
+	@Test
+	public void testFormatCondition() {
+		assertEquals("1", new StringFormatter("{?{}:{}}").format(false, 0, 1));
+	}
+	
+	@Test
+	public void testFormatNumberedCondition() {
+		assertEquals("0 word + 1 word/2 levels", StringHelper.format("{0} {2} + {1} {2}/{3?level:{4} levels}", 0, 1, "word", false, 2));
+	}
+	
+	@Test
+	public void testFormatIntegerCompareCondition() {
+		assertEquals("true", StringHelper.format("{>1?true:false}", 2));
+	}
+
+}