diff options
Diffstat (limited to 'src/main')
9 files changed, 199 insertions, 184 deletions
diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/ClassifiedParserMain.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/ClassifiedParserMain.groovy deleted file mode 100644 index 499a37a..0000000 --- a/src/main/groovy/org/xapek/yvesf/classifieds/ClassifiedParserMain.groovy +++ /dev/null @@ -1,87 +0,0 @@ -package org.xapek.yvesf.classifieds - -import groovy.json.JsonSlurper -import groovyx.net.http.HTTPBuilder -import org.apache.http.HttpResponse -import org.xapek.yvesf.classifieds.util.Fail -import org.xapek.yvesf.classifieds.util.Failable -import org.xapek.yvesf.classifieds.util.Success - -class ClassifiedParserMain { - static final url = 'http://www.glocals.com/classifieds/housing-and-real-estate/&get_classified_flats' - - static void main(String[] args) { - handleData(args.length == 1 ? readJsonFile(args.first()) : readNetwork()).with { - if (it instanceof Fail) { - println "Failed: ${it.error}" - System.exit(1) - } else { - Dumper.dump(it.value, new PrintWriter(System.out)) - System.exit(0) - } - } - } - - static Object readJsonFile(String f) { - final jsonParser = new JsonSlurper() - return jsonParser.parse(new File(f)) - } - - static Object readNetwork() { - final http = new HTTPBuilder(url) - final jsonParser = new JsonSlurper() - http.post( - body: ['start': '20', 'limit': '20', 'form[bl_city_network]': '-1'], - headers: ['User-Agent': 'Mozilla/5.0 Firefox/3.0.4', - 'Accept' : 'application/json']) { HttpResponse resp -> - assert resp.statusLine.statusCode < 300 && resp.statusLine.statusCode >= 200: - "HTTP Request failed with status code ${resp.statusLine.statusCode}" - return jsonParser.parse(resp.entity.content) - } as Object - } - - private static Failable<Model.ClassifiedList> handleData(Object p) { - expectMap(p) << { Map m -> - getOrFail(m, 'totalCount', Number) << { Number totalCount -> - getOrFail(m, 'classifieds', List) << { List rawClassifieds -> - all(rawClassifieds.collect { handleClassified(it) }) << { List<Model.Classified> classifieds -> - final Model.ClassifiedList classifiedList = new Model.ClassifiedList(totalCount: totalCount) - classifiedList.addAll(classifieds) - return new Success(classifiedList) - } - } - } - } - } - - private static Failable<Model.Classified> handleClassified(Object p) { - expectMap(p) << { Map map -> - return new Success(new Model.Classified(map)) // here I cheat a bit - } - } - - static <T> Failable<List<T>> all(Collection<Failable<T>> values) { - if (values.every { it.success }) { - return new Success<List<T>>(values.collect { it.value }) - } else { - return new Fail("Not all values are successful, first error: ${values.find { !it.success }.error}") - } - } - - static Failable<Map> expectMap(Object data) { - if (data instanceof Map) return new Success(data) - else return new Fail('object is not instanceof Map') - } - - static <T> Failable<T> getOrFail(Map map, String key, Class<T> clazz = Object) { - if (!map.containsKey(key)) return new Fail('key not in map') - else { - final x = map.get(key) - if (clazz.isAssignableFrom(x.getClass())) { - return new Success<T>(x as T) - } else { - return new Fail("Key ${key} found but not of type ${clazz}") - } - } - } -} diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/Dumper.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/Dumper.groovy index 884166d..5fb0568 100644 --- a/src/main/groovy/org/xapek/yvesf/classifieds/Dumper.groovy +++ b/src/main/groovy/org/xapek/yvesf/classifieds/Dumper.groovy @@ -1,11 +1,11 @@ package org.xapek.yvesf.classifieds import groovy.xml.MarkupBuilder -import org.xapek.yvesf.classifieds.Model.ClassifiedList +import org.xapek.yvesf.classifieds.Model.ClassifiedsList class Dumper { - static dump(ClassifiedList classifiedList, PrintWriter writer) { + static dump(ClassifiedsList classifiedList, PrintWriter writer) { new MarkupBuilder(writer).root { rss(version: '2.0') { channel { @@ -15,7 +15,7 @@ class Dumper { item { title("${classified.type} - ${classified.title} - ${classified.composedLocation}") guid(classified.id) - author(classified.memberName) + author(classified.mem_name) description(classified.description) } } diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/Main.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/Main.groovy new file mode 100644 index 0000000..23fb434 --- /dev/null +++ b/src/main/groovy/org/xapek/yvesf/classifieds/Main.groovy @@ -0,0 +1,65 @@ +package org.xapek.yvesf.classifieds + +import groovy.json.JsonSlurper +import groovy.transform.CompileStatic +import groovyx.net.http.HTTPBuilder +import org.apache.http.HttpResponse +import org.xapek.yvesf.classifieds.util.Failable + +import static org.xapek.yvesf.classifieds.util.ParserStaticMethods.* + +@CompileStatic +final class Main { + private final static JsonSlurper jsonParser = new JsonSlurper() + + private final static url = 'http://www.glocals.com/classifieds/housing-and-real-estate/&get_classified_flats' + + static void main(String[] args) { + final data = handleData( + args.length == 1 ? + readInputStream(new FileInputStream(args[0])) : readNetwork()); + if (data.isSuccess()) { + Dumper.dump(data.getValue(), new PrintWriter(System.out)); + System.exit(0); + } else { + System.out.println("Failed: " + data.errorTrace.join(' -> ')) + System.exit(1); + } + } + + static Object readInputStream(InputStream is) { + return jsonParser.parse(is) + } + + static Object readNetwork() { + final http = new HTTPBuilder(url) + http.post( + body: ['start': '20', 'limit': '20', 'form[bl_city_network]': '-1'], + headers: ['User-Agent': 'Mozilla/5.0 Firefox/3.0.4', + 'Accept' : 'application/json']) { HttpResponse resp -> + assert resp.statusLine.statusCode < 300 && resp.statusLine.statusCode >= 200: + "HTTP Request failed with status code ${resp.statusLine.statusCode}" + return jsonParser.parse(resp.entity.content) + } as Object + } + + static Failable<Model.ClassifiedsList> handleData(Object object) { + ifType(object, Map) then { Map m -> + ifField(m, 'totalCount', Number) then { Number totalCount -> + ifField(m, 'classifieds', List) then { List rawClassifieds -> + all(rawClassifieds.collect { handleClassified(it) }) then { List<Model.Classified> classifieds -> + final classifiedList = new Model.ClassifiedsList(totalCount: totalCount) + classifiedList.addAll(classifieds) + success(classifiedList) + } + } + } + } + } + + private static Failable<Model.Classified> handleClassified(Object p) { + ifType(p, Map) then { Map map -> + success(new Model.Classified(map)) + } + } +}
\ No newline at end of file diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/Model.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/Model.groovy index ee0b11d..cd62c25 100644 --- a/src/main/groovy/org/xapek/yvesf/classifieds/Model.groovy +++ b/src/main/groovy/org/xapek/yvesf/classifieds/Model.groovy @@ -1,5 +1,8 @@ package org.xapek.yvesf.classifieds +import groovy.transform.CompileStatic + +@CompileStatic class Model { static class Classified { //0 = available" -> "Flexible" @@ -32,67 +35,38 @@ class Model { //27 ="title_js" -> "Fully furnished house of 270 sqm and huge terrace of 90 sqm" //28 ="type" -> "Apts / Housing for rent" //29 ="views" -> "8" - private Map map - - Classified(Map m) { - this.@map = m - } - - Map getMap() { - return map + String available + String location + String city + String type + String price + String currency + String views + String title + String id + String mem_name + String description + + Classified(Map map) { + metaClass.properties.each { MetaProperty property -> + if (map.containsKey(property.name)) { + final value = map.get(property.name) + if (property.type.isAssignableFrom(value.getClass())) { + property.setProperty(this, value) + } else { + throw new IllegalArgumentException("Cannot set field ${property.name} with object of type ${value.getClass()}") + } + } + } } String getComposedLocation() { - return "${map.get('location')}, ${map.get('city')}" - } - - String getType() { - return map.get('type') as String - } - - String getAvailable() { - return map.get('available') as String - } - - String getCity() { - return map.get('city') - } - - String getPrice() { - return map.get('price') as String - } - - String getCurrency() { - return map.get('currency') as String - } - - String getViews() { - return map.get('views') as String - } - - String getTitle() { - return map.get('title') as String - } - - String getId() { - return map.get('id') as String - } - - String getMemberName() { - return map.get('mem_name') as String - } - - String getDescription() { - return map.get('description') as String + return "${location}, ${city}" } } - static class ClassifiedList extends ArrayList<Classified> { - private long totalCount - - void setTotalCount(long totalCount) { - this.@totalCount = totalCount - } + static class ClassifiedsList extends ArrayList<Classified> { + long totalCount @Override String toString() { diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/util/Fail.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/util/Fail.groovy index c58d6ef..b514385 100644 --- a/src/main/groovy/org/xapek/yvesf/classifieds/util/Fail.groovy +++ b/src/main/groovy/org/xapek/yvesf/classifieds/util/Fail.groovy @@ -3,19 +3,15 @@ package org.xapek.yvesf.classifieds.util import groovy.transform.CompileStatic import groovy.transform.ToString - @CompileStatic -@Newify @ToString(includeFields = true, includeNames = true) -class Fail<T> implements Failable<T> { - private String error - - Fail() { - this.@error = null - } +class Fail<T> implements Failable.FailableImpl<T> { + private List<String> errorTrace = new ArrayList<>() - Fail(String error) { - this.@error = error; + protected Fail(String step) { + if (step) { + prependTrace(step) + } } @Override @@ -24,17 +20,22 @@ class Fail<T> implements Failable<T> { } @Override - Object getValue() { + T getValue() { return null } @Override - String getError() { - return error + public <T2> Failable<T2> then(Closure<Failable<T2>> closure) { + return this } @Override - def <T2> Failable<T2> leftShift(Closure<Failable<T2>> closure) { - return null + List<String> getErrorTrace() { + return errorTrace + } + + @Override + void prependTrace(String p) { + errorTrace.add(0, p) } } diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.groovy deleted file mode 100644 index 4ad5e90..0000000 --- a/src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.groovy +++ /dev/null @@ -1,14 +0,0 @@ -package org.xapek.yvesf.classifieds.util - -import groovy.transform.CompileStatic - -@CompileStatic -interface Failable<T> { - boolean isSuccess() - - T getValue() - - String getError() - - public <T2> Failable<T2> leftShift(Closure<Failable<T2>> closure); -}
\ No newline at end of file diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.java b/src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.java new file mode 100644 index 0000000..2e1b780 --- /dev/null +++ b/src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.java @@ -0,0 +1,19 @@ +package org.xapek.yvesf.classifieds.util; + +import groovy.lang.Closure; + +import java.util.List; + +public interface Failable<T> { + boolean isSuccess(); + + T getValue(); + + <T2> Failable<T2> then(Closure<Failable<T2>> closure); + + List<String> getErrorTrace(); + + interface FailableImpl<T> extends Failable<T> { + void prependTrace(String p); + } +} diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/util/ParserStaticMethods.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/util/ParserStaticMethods.groovy new file mode 100644 index 0000000..15524c8 --- /dev/null +++ b/src/main/groovy/org/xapek/yvesf/classifieds/util/ParserStaticMethods.groovy @@ -0,0 +1,43 @@ +package org.xapek.yvesf.classifieds.util + +import groovy.transform.CompileStatic +import org.xapek.yvesf.classifieds.util.Fail +import org.xapek.yvesf.classifieds.util.Failable +import org.xapek.yvesf.classifieds.util.Success + +@CompileStatic +class ParserStaticMethods { + static <T> Failable<List<T>> all(Collection<Failable<T>> values) { + if (values.every { it.success }) { + return success(values.collect { it.value }, "all[size=${values.size()}]") + } else { + return fail("all[firstIndex=${values.findIndexOf { Failable<T> it -> !it.success }}," + + "firstTrace=${values.find { !it.success }.errorTrace.join(' -> ')}]") + } + } + + static <T> Failable<T> ifType(Object data, Class<T> type) { + if (type.isAssignableFrom(data.getClass())) success(data as T, "iftype[${type.name}]") + else fail("iftype[${type.name}]") + } + + static <T> Failable<T> ifField(Map map, String key, Class<T> clazz = Object) { + if (!map.containsKey(key)) return new Fail("ifField[notfound ${key}]") + else { + final x = map.get(key) + if (clazz.isAssignableFrom(x.getClass())) { + success(x as T, "ifField[${key}<=>${clazz}]") + } else { + fail("ifField[${key}<=>${clazz}]") + } + } + } + + static <T> Success<T> success(T value, String trace = null) { + return new Success<T>(value, trace) + } + + static <T> Fail<T> fail(String trace = null) { + return new Fail<T>(trace) + } +} diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/util/Success.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/util/Success.groovy index 224909d..9e39527 100644 --- a/src/main/groovy/org/xapek/yvesf/classifieds/util/Success.groovy +++ b/src/main/groovy/org/xapek/yvesf/classifieds/util/Success.groovy @@ -3,15 +3,15 @@ package org.xapek.yvesf.classifieds.util import groovy.transform.CompileStatic import groovy.transform.ToString - @CompileStatic -@Newify @ToString(includeFields = true, includeNames = true) -class Success<T> implements Failable<T> { - private T value +class Success<T> implements Failable.FailableImpl<T> { + final private T value + final private String step - public Success(T value) { - this.value = value + protected Success(T value, String step) { + this.@value = value + this.@step = step } @Override @@ -25,12 +25,26 @@ class Success<T> implements Failable<T> { } @Override - String getError() { - return null + public <T2> Failable<T2> then(Closure<Failable<T2>> closure) { + try { + final failable = closure.call(value) + if (!failable.success) { + (failable as Failable.FailableImpl<T>).prependTrace(step) + } + return failable + } catch (Exception e) { + final failable = new Fail("exception[${e.getClass().name}]") + failable.prependTrace(step) + return failable + } + } + + @Override + List<String> getErrorTrace() { + return [] } @Override - def <T2> Failable<T2> leftShift(Closure<Failable<T2>> closure) { - return closure.call(value) + void prependTrace(String p) { } }
\ No newline at end of file |