Merge pull request #112 from dvdsgl/java

Java

Original commit f4e9eccefc4f63e929e29acf479bd743208f23e3
This commit is contained in:
Mark Probst 2017-08-18 16:02:02 -07:00 коммит произвёл GitHub
Родитель 6313613c49 bae600c1d1
Коммит 8968a01219
16 изменённых файлов: 468 добавлений и 5 удалений

Просмотреть файл

@ -7,6 +7,8 @@ addons:
key_url: 'https://apt-mo.trafficmanager.net/keys/microsoft.asc'
packages:
- dotnet-dev-1.0.1
- maven
- default-jdk
script:
- test/test
cache:

3
.vscode/settings.json поставляемый
Просмотреть файл

@ -14,5 +14,6 @@
"app/build": true,
"elm-stuff": true,
"cli/dist": true
}
},
"java.configuration.updateBuildConfiguration": "automatic"
}

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "quicktype",
"version": "0.7.0",
"version": "0.8.0",
"dependencies": {
"chalk": "^2.1.0",
"command-line-args": "^4.0.6",

Просмотреть файл

@ -155,10 +155,45 @@ class Run {
return Either.fromRight(pipeline(input));
}
splitAndWriteJava = (dir: string, str: string) => {
const lines = str.split("\n");
let filename : string | null = null;
let currentFileContents : string = "";
const writeFile = () => {
if (filename != null) {
fs.writeFileSync(path.join(dir, filename), currentFileContents);
}
filename = null;
currentFileContents = "";
};
let i = 0;
while (i < lines.length) {
const line = lines[i];
i += 1;
const results = line.match("^// (.+\\.java)$");
if (results == null) {
currentFileContents += line + "\n";
} else {
writeFile();
filename = results[1];
while (lines[i] == "")
i++;
}
}
writeFile();
}
renderAndOutput = (jsonArrayMap: JsonArrayMap) => {
let output = this.renderFromJsonArrayMap(jsonArrayMap);
if (this.options.out) {
fs.writeFileSync(this.options.out, output);
if (this.options.lang == "java") {
this.splitAndWriteJava(path.dirname(this.options.out), output);
} else {
fs.writeFileSync(this.options.out, output);
}
} else {
process.stdout.write(output);
}

283
src/Java.purs Normal file
Просмотреть файл

@ -0,0 +1,283 @@
module Java
( renderer
) where
import Doc
import IRGraph
import Prelude
import Data.Array as A
import Data.Char.Unicode (GeneralCategory(..), generalCategory, isSpace)
import Data.Foldable (find, for_)
import Data.Map (Map)
import Data.Map as M
import Data.Maybe (Maybe(..), isJust, isNothing)
import Data.Set (Set)
import Data.Set as S
import Data.String.Util (camelCase, decapitalize, isLetterOrLetterNumber, legalizeCharacters, startWithLetter, stringEscape)
import Data.Tuple (Tuple(..))
import Utils (removeElement)
forbiddenNames :: Array String
forbiddenNames =
[ "Object", "Class", "System", "Long", "Double", "Boolean", "String", "Map", "Exception", "IOException"
, "JsonProperty", "JsonDeserialize", "JsonDeserializer", "JsonSerialize", "JsonSerializer"
, "JsonParser", "JsonProcessingException", "DeserializationContext", "SerializerProvider"
, "Converter"
]
renderer :: Renderer
renderer =
{ name: "Java"
, aceMode: "java"
, extension: "java"
, doc: javaDoc
, transforms:
{ nameForClass: simpleNamer nameForClass
, nextName: \s -> "Other" <> s
, forbiddenNames: forbiddenNames
, topLevelName: noForbidNamer javaNameStyle
, unions: Just
{ predicate: unionIsNotSimpleNullable
, properName: simpleNamer (javaNameStyle <<< combineNames)
, nameFromTypes: simpleNamer (unionNameIntercalated javaNameStyle "Or")
}
}
}
nameForClass :: IRClassData -> String
nameForClass (IRClassData { names }) = javaNameStyle $ combineNames names
isStartCharacter :: Char -> Boolean
isStartCharacter c =
case generalCategory c of
Just CurrencySymbol -> true
Just ConnectorPunctuation -> true
_ -> isLetterOrLetterNumber c
isPartCharacter :: Char -> Boolean
isPartCharacter c =
case generalCategory c of
Just DecimalNumber -> true
Just SpacingCombiningMark -> true
Just NonSpacingMark -> true
Just Format -> true
Just Control -> not $ isSpace c
_ -> isStartCharacter c
javaNameStyle :: String -> String
javaNameStyle = legalizeCharacters isPartCharacter >>> camelCase >>> startWithLetter isStartCharacter true
javaDoc :: Doc Unit
javaDoc = do
renderConverter
forEachClass_ \className properties -> do
blank
renderClassDefinition className properties
forEachUnion_ \unionName unionTypes -> do
blank
renderUnionDefinition unionName unionTypes
renderUnionWithTypeRenderer :: (Boolean -> IRType -> Doc String) -> IRUnionRep -> Doc String
renderUnionWithTypeRenderer typeRenderer ur =
case nullableFromSet $ unionToSet ur of
Just x -> typeRenderer true x
Nothing -> lookupUnionName ur
renderUnion :: IRUnionRep -> Doc String
renderUnion = renderUnionWithTypeRenderer renderType
renderType :: Boolean -> IRType -> Doc String
renderType reference = case _ of
IRNothing -> pure "Object"
IRNull -> pure "Object"
IRInteger -> pure $ if reference then "Long" else "long"
IRDouble -> pure $ if reference then "Double" else "double"
IRBool -> pure $ if reference then "Boolean" else "boolean"
IRString -> pure "String"
IRArray t -> do
rendered <- renderType false t
pure $ rendered <> "[]"
IRClass i -> lookupClassName i
IRMap t -> do
rendered <- renderType true t
pure $ "Map<String, " <> rendered <> ">"
IRUnion ur -> renderUnionWithTypeRenderer renderType ur
renderTypeWithoutGenerics :: Boolean -> IRType -> Doc String
renderTypeWithoutGenerics reference = case _ of
IRArray t -> do
rendered <- renderTypeWithoutGenerics false t
pure $ rendered <> "[]"
IRMap t -> pure "Map"
IRUnion ur -> renderUnionWithTypeRenderer renderTypeWithoutGenerics ur
t -> renderType reference t
fieldNameForJavaName :: String -> String
fieldNameForJavaName = decapitalize >>> ("_" <> _)
forEachProperty_ :: Map String IRType -> Map String String -> (String -> IRType -> String -> String -> String -> Doc Unit) -> Doc Unit
forEachProperty_ properties propertyNames f =
for_ (M.toUnfoldable properties :: Array _) \(Tuple pname ptype) -> do
let javaName = lookupName pname propertyNames
let fieldName = fieldNameForJavaName javaName
rendered <- renderType false ptype
f pname ptype javaName fieldName rendered
renderFileHeader :: String -> Array String -> Doc Unit
renderFileHeader fileName imports = do
line $ "// " <> fileName <> ".java"
blank
line "package io.quicktype;"
blank
for_ imports \package -> do
line $ "import " <> package <> ";"
blank
getDecoderHelperPrefix :: String -> Doc String
getDecoderHelperPrefix topLevelName = getForSingleOrMultipleTopLevels "" topLevelName
renderConverter :: Doc Unit
renderConverter = do
renderFileHeader "Converter" ["java.util.Map", "java.io.IOException", "com.fasterxml.jackson.databind.ObjectMapper", "com.fasterxml.jackson.core.JsonProcessingException"]
line "public class Converter {"
indent do
line "// Serialize/deserialize helpers"
forEachTopLevel_ \topLevelName topLevelType -> do
blank
topLevelTypeRendered <- renderType false topLevelType
fromJsonPrefix <- getDecoderHelperPrefix topLevelName
line $ "public static " <> topLevelTypeRendered <> " " <> fromJsonPrefix <> "FromJsonString(String json) throws IOException {"
indent do
line "ObjectMapper mapper = new ObjectMapper();"
renderedForClass <- renderTypeWithoutGenerics false topLevelType
line $ "return mapper.readValue(json, " <> renderedForClass <> ".class);"
line "}"
blank
line $ "public static String " <> fromJsonPrefix <> "ToJsonString(" <> topLevelTypeRendered <> " obj) throws JsonProcessingException {"
indent do
line "ObjectMapper mapper = new ObjectMapper();"
line "return mapper.writeValueAsString(obj);"
line "}"
line "}"
renderClassDefinition :: String -> Map String IRType -> Doc Unit
renderClassDefinition className properties = do
renderFileHeader className ["java.util.Map", "com.fasterxml.jackson.annotation.*"]
let propertyNames = transformPropertyNames (simpleNamer javaNameStyle) ("Other" <> _) ["Class"] properties
when (M.isEmpty properties) do
line "@JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.NONE)"
line $ "public class " <> className <> " {"
indent do
forEachProperty_ properties propertyNames \pname ptype javaName fieldName rendered -> do
line $ "private " <> rendered <> " " <> fieldName <> ";"
forEachProperty_ properties propertyNames \pname ptype javaName fieldName rendered -> do
blank
line $ "@JsonProperty(\"" <> stringEscape pname <> "\")"
line $ "public " <> rendered <> " get" <> javaName <> "() { return " <> fieldName <> "; }"
line $ "public void set" <> javaName <> "(" <> rendered <> " value) { " <> fieldName <> " = value; }"
line "}"
renderUnionField :: IRType -> Doc { renderedType :: String, fieldName :: String }
renderUnionField t = do
renderedType <- renderType true t
fieldName <- decapitalize <$> javaNameStyle <$> (_ <> "_value") <$> getTypeNameForUnion t
pure { renderedType, fieldName }
tokenCase :: String -> Doc Unit
tokenCase tokenType =
line $ "case " <> tokenType <> ":"
renderNullCase :: Set IRType -> Doc Unit
renderNullCase types =
when (S.member IRNull types) do
tokenCase "VALUE_NULL"
indent do
line "break;"
deserializeType :: IRType -> Doc Unit
deserializeType t = do
{ fieldName } <- renderUnionField t
renderedType <- renderTypeWithoutGenerics true t
line $ "value." <> fieldName <> " = jsonParser.readValueAs(" <> renderedType <> ".class);"
line "break;"
renderPrimitiveCase :: Array String -> IRType -> Set IRType -> Doc Unit
renderPrimitiveCase tokenTypes t types =
when (S.member t types) do
for_ tokenTypes \tokenType ->
tokenCase tokenType
indent do
deserializeType t
renderDoubleCase :: Set IRType -> Doc Unit
renderDoubleCase types =
when (S.member IRDouble types) do
unless (S.member IRInteger types) do
tokenCase "VALUE_NUMBER_INT"
tokenCase "VALUE_NUMBER_FLOAT"
indent do
deserializeType IRDouble
renderGenericCase :: (IRType -> Boolean) -> String -> Set IRType -> Doc Unit
renderGenericCase predicate tokenType types =
case find predicate types of
Nothing -> pure unit
Just t -> do
tokenCase tokenType
indent do
deserializeType t
renderUnionDefinition :: String -> Set IRType -> Doc Unit
renderUnionDefinition unionName unionTypes = do
let { element: emptyOrNull, rest: nonNullTypes } = removeElement (_ == IRNull) unionTypes
renderFileHeader unionName ["java.io.IOException", "java.util.Map", "com.fasterxml.jackson.core.*", "com.fasterxml.jackson.databind.*", "com.fasterxml.jackson.databind.annotation.*"]
line $ "@JsonDeserialize(using = " <> unionName <> ".Deserializer.class)"
line $ "@JsonSerialize(using = " <> unionName <> ".Serializer.class)"
line $ "public class " <> unionName <> " {"
indent do
for_ nonNullTypes \t -> do
{ renderedType, fieldName } <- renderUnionField t
line $ "public " <> renderedType <> " " <> fieldName <> ";"
blank
line $ "static class Deserializer extends JsonDeserializer<" <> unionName <> "> {"
indent do
line "@Override"
line $ "public " <> unionName <> " deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {"
indent do
line $ unionName <> " value = new " <> unionName <> "();"
line "switch (jsonParser.getCurrentToken()) {"
renderNullCase unionTypes
renderPrimitiveCase ["VALUE_NUMBER_INT"] IRInteger unionTypes
renderDoubleCase unionTypes
renderPrimitiveCase ["VALUE_TRUE", "VALUE_FALSE"] IRBool unionTypes
renderPrimitiveCase ["VALUE_STRING"] IRString unionTypes
renderGenericCase isArray "START_ARRAY" unionTypes
renderGenericCase isClass "START_OBJECT" unionTypes
renderGenericCase isMap "START_OBJECT" unionTypes
line $ "default: throw new IOException(\"Cannot deserialize " <> unionName <> "\");"
line "}"
line "return value;"
line "}"
line "}"
blank
line $ "static class Serializer extends JsonSerializer<" <> unionName <> "> {"
indent do
line "@Override"
line $ "public void serialize(" <> unionName <> " obj, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {"
indent do
for_ (A.fromFoldable nonNullTypes) \field -> do
{ fieldName } <- renderUnionField field
line $ "if (obj." <> fieldName <> " != null) {"
indent do
line $ "jsonGenerator.writeObject(obj." <> fieldName <> ");"
line "return;"
line "}"
when (isJust emptyOrNull) do
line "jsonGenerator.writeNull();"
when (isNothing emptyOrNull) do
line $ "throw new IOException(\"" <> unionName <> " must not be null\");"
line "}"
line "}"
line "}"

Просмотреть файл

@ -33,6 +33,7 @@ import Elm as Elm
import Environment (Environment(..))
import Environment as Env
import Golang as Golang
import Java as Java
import JsonSchema (JSONSchema, jsonSchemaListToIR)
import JsonSchema as JsonSchema
import UrlGrammar (GrammarMap(..), generate)
@ -56,6 +57,7 @@ renderers =
[ TypeScript.renderer
, Golang.renderer
, CSharp.renderer
, Java.renderer
, Elm.renderer
, Pseudocode.renderer
, JsonSchema.renderer

2
test/.gitignore поставляемый
Просмотреть файл

@ -1,2 +1,4 @@
/node_modules
/dist
/java/.idea/workspace.xml
/java/out

26
test/fixtures/java/.classpath поставляемый Normal file
Просмотреть файл

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

23
test/fixtures/java/.project поставляемый Normal file
Просмотреть файл

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>QuickTypeTest</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

5
test/fixtures/java/.settings/org.eclipse.jdt.core.prefs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
org.eclipse.jdt.core.compiler.compliance=1.5
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.5

4
test/fixtures/java/.settings/org.eclipse.m2e.core.prefs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

35
test/fixtures/java/pom.xml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,35 @@
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.quicktype</groupId>
<artifactId>QuickTypeTest</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>QuickTypeTest</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<scope>compile</scope>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

22
test/fixtures/java/src/main/java/io/quicktype/App.java поставляемый Normal file
Просмотреть файл

@ -0,0 +1,22 @@
package io.quicktype;
import java.nio.file.*;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
try {
String input = new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(args[0])), "UTF-8");
String output = Converter.ToJsonString(Converter.FromJsonString(input));
System.out.println(output);
} catch (Exception exc) {
System.err.printf("Error: %s\n", exc.getMessage());
System.exit(1);
}
}
}

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -65,6 +65,18 @@ const FIXTURES: Fixture[] = [
topLevel: "QuickType",
test: testCSharp
},
{
name: "java",
base: "test/fixtures/java",
diffViaSchema: false,
output: "src/main/java/io/quicktype/TopLevel.java",
topLevel: "TopLevel",
test: testJava,
skip: [
"identifiers.json",
"simple-identifiers.json"
]
},
{
name: "golang",
base: "test/fixtures/golang",
@ -141,6 +153,19 @@ async function testCSharp(sample: string) {
});
}
//////////////////////////////////////
// Java tests
/////////////////////////////////////
async function testJava(sample: string) {
exec(`mvn package`);
compareJsonFileToJson({
expectedFile: sample,
jsonCommand: `java -cp target/QuickTypeTest-1.0-SNAPSHOT.jar io.quicktype.App "${sample}"`,
strict: false
});
}
//////////////////////////////////////
// Elm tests
/////////////////////////////////////