diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/io/NetworkUtils.java b/OsmAnd-java/src/main/java/net/osmand/osm/io/NetworkUtils.java index 28a1ec3fb9..1590116f5c 100644 --- a/OsmAnd-java/src/main/java/net/osmand/osm/io/NetworkUtils.java +++ b/OsmAnd-java/src/main/java/net/osmand/osm/io/NetworkUtils.java @@ -56,6 +56,55 @@ public class NetworkUtils { return e.getMessage(); } } + + public static String sendPostDataRequest(String urlText, InputStream data){ + try { + log.info("POST : " + urlText); + HttpURLConnection conn = getHttpURLConnection(urlText); + conn.setDoInput(true); + conn.setDoOutput(false); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Accept","*/*"); + conn.setRequestProperty("User-Agent", "OsmAnd"); //$NON-NLS-1$ //$NON-NLS-2$ + conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); + OutputStream ous = conn.getOutputStream(); + ous.write(("--" + BOUNDARY + "\r\n").getBytes()); + ous.write(("content-disposition: form-data; name=\"" + "file" + "\"; filename=\"" + "image1" + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$ + ous.write(("Content-Type: application/octet-stream\r\n\r\n").getBytes()); //$NON-NLS-1$ + Algorithms.streamCopy(data,ous); + ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$ + ous.flush(); + log.info("Response code and message : " + conn.getResponseCode() + " " + conn.getResponseMessage()); + if(conn.getResponseCode() != 200){ + return conn.getResponseMessage(); + } + StringBuilder responseBody = new StringBuilder(); + InputStream is = conn.getInputStream(); + responseBody.setLength(0); + if (is != null) { + BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8")); //$NON-NLS-1$ + String s; + boolean first = true; + while ((s = in.readLine()) != null) { + if(first){ + first = false; + } else { + responseBody.append("\n"); //$NON-NLS-1$ + } + responseBody.append(s); + } + is.close(); + } + Algorithms.closeStream(is); + Algorithms.closeStream(data); + Algorithms.closeStream(ous); + return responseBody.toString(); + } catch (IOException e) { + log.error(e.getMessage(), e); + return e.getMessage(); + } + } + private static final String BOUNDARY = "CowMooCowMooCowCowCow"; //$NON-NLS-1$ public static String uploadFile(String urlText, File fileToUpload, String userNamePassword, OsmOAuthAuthorizationClient client, diff --git a/OsmAnd/libs/opendb-core.jar b/OsmAnd/libs/opendb-core.jar new file mode 100644 index 0000000000..ac14553f6d Binary files /dev/null and b/OsmAnd/libs/opendb-core.jar differ diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java index 00d0a8cc20..affeba63d1 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuBuilder.java @@ -34,17 +34,20 @@ import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import net.osmand.AndroidUtils; +import net.osmand.PlatformUtil; import net.osmand.binary.BinaryMapIndexReader; import net.osmand.data.Amenity; import net.osmand.data.LatLon; import net.osmand.data.PointDescription; import net.osmand.data.QuadRect; import net.osmand.osm.PoiCategory; +import net.osmand.osm.io.NetworkUtils; import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.UiUtilities; +import net.osmand.plus.activities.ActivityResultListener; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.FontCache; import net.osmand.plus.mapcontextmenu.builders.cards.AbstractCard; @@ -53,6 +56,7 @@ import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard; import net.osmand.plus.mapcontextmenu.builders.cards.ImageCard.GetImageCardsTask; import net.osmand.plus.mapcontextmenu.builders.cards.NoImagesCard; import net.osmand.plus.mapcontextmenu.controllers.TransportStopController; +import net.osmand.plus.osmedit.utils.SecUtils; import net.osmand.plus.poi.PoiUIFilter; import net.osmand.plus.render.RenderingIcons; import net.osmand.plus.transport.TransportStopRoute; @@ -62,7 +66,9 @@ import net.osmand.plus.widgets.TextViewEx; import net.osmand.plus.widgets.tools.ClickableSpanTouchListener; import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; - +import org.apache.commons.logging.Log; +import java.io.FileNotFoundException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -77,7 +83,7 @@ public class MenuBuilder { public static final float SHADOW_HEIGHT_TOP_DP = 17f; public static final int TITLE_LIMIT = 60; - protected static final String[] arrowChars = new String[]{"=>"," - "}; + protected static final String[] arrowChars = new String[] {"=>", " - "}; protected MapActivity mapActivity; protected MapContextMenu mapContextMenu; @@ -103,6 +109,8 @@ public class MenuBuilder { private String preferredMapLang; private String preferredMapAppLang; private boolean transliterateNames; + private static final int PICK_IMAGE = 1231; + private static final Log LOG = PlatformUtil.getLog(MenuBuilder.class); public interface CollapseExpandListener { void onCollapseExpand(boolean collapsed); @@ -212,10 +220,59 @@ public class MenuBuilder { if (showOnlinePhotos) { buildNearestPhotosRow(view); } + buildUploadImagesRow(view); buildPluginRows(view); // buildAfter(view); } + public void buildUploadImagesRow(View view) { + if (mapContextMenu != null) { + //TODO to strings + String title = "Upload images"; + buildRow(view, R.drawable.ic_action_note_dark, null, title, 0, false, + null, false, 0, false, new OnClickListener() { + @Override + public void onClick(View view) { + mapActivity.registerActivityResultListener(new ActivityResultListener(PICK_IMAGE, + new ActivityResultListener.OnActivityResultListener() { + @Override + public void onResult(int resultCode, Intent resultData) { + InputStream inputStream = null; + try { + inputStream = mapActivity.getContentResolver().openInputStream(resultData.getData()); + } catch (FileNotFoundException e) { + LOG.error(e); + } + handleSelectedImage(inputStream); + } + })); + Intent intent = new Intent(); + intent.setType("image/*"); + intent.setAction(Intent.ACTION_GET_CONTENT); + mapActivity.startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_IMAGE); + } + }, false); + } + } + + private void handleSelectedImage(final InputStream image) { + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try{ + String url = "https://test.openplacereviews.org/api/ipfs/image"; + String response = NetworkUtils.sendPostDataRequest(url, image); + //TODO + (new SecUtils()).main(new String[0]); + } + catch (Exception e){ + e.printStackTrace(); + } + } + }); + t.start(); + } + private boolean showTransportRoutes() { return showLocalTransportRoutes() || showNearbyTransportRoutes(); } @@ -254,7 +311,7 @@ public class MenuBuilder { protected boolean needBuildPlainMenuItems() { return true; } - + protected boolean needBuildCoordinatesRow() { return true; } @@ -282,7 +339,7 @@ public class MenuBuilder { protected void buildNearestWikiRow(View view) { if (processNearestWiki() && nearestWiki.size() > 0) { - buildRow(view, R.drawable.ic_action_wikipedia, null, app.getString(R.string.wiki_around) + " (" + nearestWiki.size()+")", 0, + buildRow(view, R.drawable.ic_action_wikipedia, null, app.getString(R.string.wiki_around) + " (" + nearestWiki.size() + ")", 0, true, getCollapsableWikiView(view.getContext(), true), false, 0, false, null, false); } @@ -322,9 +379,9 @@ public class MenuBuilder { locationData.remove(PointDescription.LOCATION_LIST_HEADER); CollapsableView cv = getLocationCollapsableView(locationData); buildRow(view, R.drawable.ic_action_get_my_location, null, title, 0, true, cv, false, 1, - false, null, false); + false, null, false); } - + private void startLoadingImages() { if (onlinePhotoCardsRow == null) { return; @@ -379,7 +436,7 @@ public class MenuBuilder { } } - protected void buildDescription(View view){ + protected void buildDescription(View view) { } protected void buildAfter(View view) { @@ -395,8 +452,8 @@ public class MenuBuilder { } public View buildRow(View view, int iconId, String buttonText, String text, int textColor, - boolean collapsable, final CollapsableView collapsableView, - boolean needLinks, int textLinesLimit, boolean isUrl, OnClickListener onClickListener, boolean matchWidthDivider) { + boolean collapsable, final CollapsableView collapsableView, + boolean needLinks, int textLinesLimit, boolean isUrl, OnClickListener onClickListener, boolean matchWidthDivider) { return buildRow(view, iconId == 0 ? null : getRowIcon(iconId), buttonText, text, textColor, null, collapsable, collapsableView, needLinks, textLinesLimit, isUrl, onClickListener, matchWidthDivider); } @@ -480,7 +537,7 @@ public class MenuBuilder { textPrefixView.setLayoutParams(llTextParams); textPrefixView.setTypeface(FontCache.getRobotoRegular(view.getContext())); textPrefixView.setTextSize(12); - textPrefixView.setTextColor(app.getResources().getColor(light ? R.color.text_color_secondary_light: R.color.text_color_secondary_dark)); + textPrefixView.setTextColor(app.getResources().getColor(light ? R.color.text_color_secondary_light : R.color.text_color_secondary_dark)); textPrefixView.setMinLines(1); textPrefixView.setMaxLines(1); textPrefixView.setText(textPrefix); @@ -526,7 +583,7 @@ public class MenuBuilder { textViewSecondary.setLayoutParams(llTextSecondaryParams); textViewSecondary.setTypeface(FontCache.getRobotoRegular(view.getContext())); textViewSecondary.setTextSize(14); - textViewSecondary.setTextColor(app.getResources().getColor(light ? R.color.text_color_secondary_light: R.color.text_color_secondary_dark)); + textViewSecondary.setTextColor(app.getResources().getColor(light ? R.color.text_color_secondary_light : R.color.text_color_secondary_dark)); textViewSecondary.setText(secondaryText); llText.addView(textViewSecondary); } @@ -581,7 +638,7 @@ public class MenuBuilder { } if (collapsableView.getContentView().getParent() != null) { ((ViewGroup) collapsableView.getContentView().getParent()) - .removeView(collapsableView.getContentView()); + .removeView(collapsableView.getContentView()); } baseView.addView(collapsableView.getContentView()); } @@ -741,8 +798,8 @@ public class MenuBuilder { } public void addPlainMenuItem(int iconId, String text, boolean needLinks, boolean isUrl, - boolean collapsable, CollapsableView collapsableView, - OnClickListener onClickListener) { + boolean collapsable, CollapsableView collapsableView, + OnClickListener onClickListener) { plainMenuItems.add(new PlainMenuItem(iconId, null, text, needLinks, isUrl, collapsable, collapsableView, onClickListener)); } @@ -964,7 +1021,7 @@ public class MenuBuilder { button.setTypeface(FontCache.getRobotoRegular(context)); int bg; if (selected) { - bg = light ? R.drawable.context_menu_controller_bg_light_selected: R.drawable.context_menu_controller_bg_dark_selected; + bg = light ? R.drawable.context_menu_controller_bg_light_selected : R.drawable.context_menu_controller_bg_dark_selected; } else if (showAll) { bg = light ? R.drawable.context_menu_controller_bg_light_show_all : R.drawable.context_menu_controller_bg_dark_show_all; } else { @@ -1021,7 +1078,7 @@ public class MenuBuilder { private List getAmenities(QuadRect rect, PoiUIFilter wikiPoiFilter) { return wikiPoiFilter.searchAmenities(rect.top, rect.left, - rect.bottom, rect.right, -1, null); + rect.bottom, rect.right, -1, null); } @SuppressWarnings("unchecked") diff --git a/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java b/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java index 393dd7d965..6e38a115ea 100644 --- a/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java +++ b/OsmAnd/src/net/osmand/plus/osmedit/OpenstreetmapRemoteUtil.java @@ -18,6 +18,7 @@ import net.osmand.osm.edit.Entity.EntityType; import net.osmand.osm.edit.EntityInfo; import net.osmand.osm.edit.Node; import net.osmand.osm.edit.Way; +import net.osmand.osm.io.Base64; import net.osmand.osm.io.NetworkUtils; import net.osmand.osm.io.OsmBaseStorage; import net.osmand.plus.OsmandApplication; @@ -30,10 +31,8 @@ import org.apache.commons.logging.Log; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; +import java.io.*; +import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.text.MessageFormat; import java.util.HashMap; @@ -108,14 +107,16 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil { boolean doAuthenticate) { log.info("Sending request " + url); //$NON-NLS-1$ try { - if (doAuthenticate){ - OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); - Response response = client.performRequest(url,requestMethod,requestBody); - return response.getBody(); - } - else { - OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); - Response response = client.performRequestWithoutAuth(url,requestMethod,requestBody); + OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx); + if (doAuthenticate) { + if (client.isValidToken()) { + Response response = client.performRequest(url, requestMethod, requestBody); + return response.getBody(); + } else { + return performBasicAuthRequest(url, requestMethod, requestBody, userOperation); + } + } else { + Response response = client.performRequestWithoutAuth(url, requestMethod, requestBody); return response.getBody(); } } catch (NullPointerException e) { @@ -139,11 +140,64 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil { log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$ showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation)); + } catch (Exception e) { + log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$ + showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template) + + ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation)); } return null; } + private String performBasicAuthRequest(String url, String requestMethod, String requestBody, String userOperation) throws IOException { + HttpURLConnection connection = NetworkUtils.getHttpURLConnection(url); + connection.setConnectTimeout(15000); + connection.setRequestMethod(requestMethod); + connection.setRequestProperty("User-Agent", Version.getFullVersion(ctx)); //$NON-NLS-1$ + StringBuilder responseBody = new StringBuilder(); + String token = settings.USER_NAME.get() + ":" + settings.USER_PASSWORD.get(); //$NON-NLS-1$ + connection.addRequestProperty("Authorization", "Basic " + Base64.encode(token.getBytes("UTF-8"))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + connection.setDoInput(true); + if (requestMethod.equals("PUT") || requestMethod.equals("POST") || requestMethod.equals("DELETE")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + connection.setDoOutput(true); + connection.setRequestProperty("Content-type", "text/xml"); //$NON-NLS-1$ //$NON-NLS-2$ + OutputStream out = connection.getOutputStream(); + if (requestBody != null) { + BufferedWriter bwr = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"), 1024); //$NON-NLS-1$ + bwr.write(requestBody); + bwr.flush(); + } + out.close(); + } + connection.connect(); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + String msg = userOperation + + " " + ctx.getString(R.string.failed_op) + " : " + connection.getResponseMessage(); //$NON-NLS-1$//$NON-NLS-2$ + log.error(msg); + showWarning(msg); + } else { + log.info("Response : " + connection.getResponseMessage()); //$NON-NLS-1$ + // populate return fields. + responseBody.setLength(0); + InputStream i = connection.getInputStream(); + if (i != null) { + BufferedReader in = new BufferedReader(new InputStreamReader(i, "UTF-8"), 256); //$NON-NLS-1$ + String s; + boolean f = true; + while ((s = in.readLine()) != null) { + if (!f) { + responseBody.append("\n"); //$NON-NLS-1$ + } else { + f = false; + } + responseBody.append(s); + } + } + return responseBody.toString(); + } + return null; + } + public long openChangeSet(String comment) { long id = -1; StringWriter writer = new StringWriter(256); diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/FailedVerificationException.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/FailedVerificationException.java new file mode 100644 index 0000000000..08f1a71cec --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/FailedVerificationException.java @@ -0,0 +1,16 @@ +package net.osmand.plus.osmedit.utils; + +public class FailedVerificationException extends Exception { + + private static final long serialVersionUID = -4936205097177668159L; + + + public FailedVerificationException(Exception e) { + super(e); + } + + + public FailedVerificationException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/JsonFormatter.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/JsonFormatter.java new file mode 100644 index 0000000000..74aca61659 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/JsonFormatter.java @@ -0,0 +1,135 @@ +package net.osmand.plus.osmedit.utils; + +import com.google.gson.*; +import net.osmand.plus.osmedit.utils.ops.OpObject; +import net.osmand.plus.osmedit.utils.ops.OpOperation; + +import java.io.Reader; +import java.lang.reflect.Type; +import java.util.*; + +public class JsonFormatter { + + private Gson gson; + + private Gson gsonOperationHash; + + private Gson gsonFullOutput; + + public JsonFormatter() { + GsonBuilder builder = new GsonBuilder(); + builder.disableHtmlEscaping(); + builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(false)); + builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(false)); + builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix()); + gson = builder.create(); + + builder = new GsonBuilder(); + builder.disableHtmlEscaping(); + builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(false, true)); + builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(false)); + builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix()); + gsonOperationHash = builder.create(); + + builder = new GsonBuilder(); + builder.disableHtmlEscaping(); + builder.registerTypeAdapter(OpOperation.class, new OpOperation.OpOperationBeanAdapter(true)); + builder.registerTypeAdapter(OpObject.class, new OpObject.OpObjectAdapter(true)); + builder.registerTypeAdapter(TreeMap.class, new MapDeserializerDoubleAsIntFix()); + gsonFullOutput = builder.create(); + + + } + + public static class MapDeserializerDoubleAsIntFix implements JsonDeserializer> { + + @Override @SuppressWarnings("unchecked") + public TreeMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return (TreeMap) read(json); + } + + public Object read(JsonElement in) { + + if(in.isJsonArray()){ + List list = new ArrayList(); + JsonArray arr = in.getAsJsonArray(); + for (JsonElement anArr : arr) { + list.add(read(anArr)); + } + return list; + }else if(in.isJsonObject()){ + Map map = new TreeMap(); + JsonObject obj = in.getAsJsonObject(); + Set> entitySet = obj.entrySet(); + for(Map.Entry entry: entitySet){ + map.put(entry.getKey(), read(entry.getValue())); + } + return map; + }else if(in.isJsonPrimitive()){ + JsonPrimitive prim = in.getAsJsonPrimitive(); + if(prim.isBoolean()){ + return prim.getAsBoolean(); + }else if(prim.isString()){ + return prim.getAsString(); + }else if(prim.isNumber()){ + Number num = prim.getAsNumber(); + // here you can handle double int/long values + // and return any type you want + // this solution will transform 3.0 float to long values + if(Math.ceil(num.doubleValue()) == num.longValue() && (!num.toString().contains(".") || num.toString().split("\\.")[1].length() <= 1)) + return num.longValue(); + else { + return num.doubleValue(); + } + } + } + return null; + } + } + +// operations to parse / format related + public OpOperation parseOperation(String opJson) { + return gson.fromJson(opJson, OpOperation.class); + } + + public OpObject parseObject(String opJson) { + return gson.fromJson(opJson, OpObject.class); + } + + public Object parseBlock(String opJson) { + throw new UnsupportedOperationException(""); + } + + public JsonElement toJsonElement(Object o) { + return gson.toJsonTree(o); + } + + @SuppressWarnings("unchecked") + public TreeMap fromJsonToTreeMap(String json) { + return gson.fromJson(json, TreeMap.class); + } + + + public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException { + return gson.fromJson(json, classOfT); + } + + public String fullObjectToJson(Object o) { + return gsonFullOutput.toJson(o); + } + + + public String opToJsonNoHash(OpOperation op) { + return gsonOperationHash.toJson(op); + } + + public String opToJson(OpOperation op) { + return gson.toJson(op); + } + + public String objToJson(OpObject op) { + return gson.toJson(op); + } + + +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/SecUtils.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/SecUtils.java new file mode 100644 index 0000000000..d0f0765bb6 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/SecUtils.java @@ -0,0 +1,411 @@ +package net.osmand.plus.osmedit.utils; + + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.*; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +//import java.util.Base64; +import java.util.concurrent.ThreadLocalRandom; + +import net.osmand.plus.osmedit.utils.ops.OpOperation; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.DigestUtils; +//import org.bouncycastle.crypto.generators.SCrypt; +//import org.bouncycastle.crypto.prng.FixedSecureRandom; +public class SecUtils { + private static final String SIG_ALGO_SHA1_EC = "SHA1withECDSA"; + private static final String SIG_ALGO_NONE_EC = "NonewithECDSA"; + + public static final String SIG_ALGO_ECDSA = "ECDSA"; + public static final String ALGO_EC = "EC"; + public static final String EC_256SPEC_K1 = "secp256k1"; + + public static final String KEYGEN_PWD_METHOD_1 = "EC256K1_S17R8"; + public static final String DECODE_BASE64 = "base64"; + public static final String HASH_SHA256 = "sha256"; + public static final String HASH_SHA1 = "sha1"; + + public static final String JSON_MSG_TYPE = "json"; + public static final String KEY_BASE64 = DECODE_BASE64; + + public static void main(String[] args) { + //1) create op, 2) sign op 3) send to server process op + // + KeyPairGenerator keyGen = null ; + SecureRandom random = null; + try { + keyGen = KeyPairGenerator.getInstance(ALGO_EC); + random = SecureRandom.getInstance("SHA1PRNG"); + keyGen.initialize(1024, random); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + KeyPair kp = null; + try { + kp = SecUtils.getKeyPair(ALGO_EC, + "base64:PKCS#8:MD4CAQAwEAYHKoZIzj0CAQYFK4EEAAoEJzAlAgEBBCDR+/ByIjTHZgfdnMfP9Ab5s14mMzFX+8DYqUiGmf/3rw==" + , "base64:X.509:MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEOMUiRZwU7wW8L3A1qaJPwhAZy250VaSxJmKCiWdn9EMeubXQgWNT8XUWLV5Nvg7O3sD+1AAQLG5kHY8nOc/AyA=="); + } catch (FailedVerificationException e) { + e.printStackTrace(); + } +// KeyPair kp = generateECKeyPairFromPassword(KEYGEN_PWD_METHOD_1, "openplacereviews", ""); +// KeyPair kp = generateRandomEC256K1KeyPair(); + System.out.println(kp.getPrivate().getFormat()); + System.out.println(kp.getPrivate().getAlgorithm()); + try { + System.out.println(SecUtils.validateKeyPair(ALGO_EC, kp.getPrivate(), kp.getPublic())); + } catch (FailedVerificationException e) { + e.printStackTrace(); + } + String pr = encodeKey(KEY_BASE64, kp.getPrivate()); + String pk = encodeKey(KEY_BASE64, kp.getPublic()); + String algo = kp.getPrivate().getAlgorithm(); + System.out.println(String.format("Private key: %s %s\nPublic key: %s %s", kp.getPrivate().getFormat(), pr, kp + .getPublic().getFormat(), pk)); + String signMessageTest = "Hello this is a registration message test"; + byte[] signature = signMessageWithKey(kp, signMessageTest.getBytes(), SIG_ALGO_SHA1_EC); + System.out.println(String.format("Signed message: %s %s", android.util.Base64.decode(signature, android.util.Base64.DEFAULT), + signMessageTest)); + + KeyPair nk = null; + try { + nk = getKeyPair(algo, pr, pk); + } catch (FailedVerificationException e) { + e.printStackTrace(); + } + // validate + pr = new String(android.util.Base64.decode(nk.getPrivate().getEncoded(), android.util.Base64.DEFAULT)); + pk = new String(android.util.Base64.decode(nk.getPublic().getEncoded(), android.util.Base64.DEFAULT)); + + System.out.println(String.format("Private key: %s %s\nPublic key: %s %s", nk.getPrivate().getFormat(), pr, nk + .getPublic().getFormat(), pk)); + System.out.println(validateSignature(nk, signMessageTest.getBytes(), SIG_ALGO_SHA1_EC, signature)); + + JsonFormatter formatter = new JsonFormatter(); + String msg = "{\n" + + " \"type\" : \"sys.signup\",\n" + + " \"signed_by\": \"openplacereviews\",\n" + + " \"create\": [{\n" + + " \"id\": [\"openplacereviews\"],\n" + + " \"name\" : \"openplacereviews\",\n" + + " \"algo\": \"EC\",\n" + + " \"auth_method\": \"provided\",\n" + + " \"pubkey\": \"base64:X.509:MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEn6GkOTN3SYc+OyCYCpqPzKPALvUgfUVNDJ+6eyBlCHI1/gKcVqzHLwaO90ksb29RYBiF4fW/PqHcECNzwJB+QA==\"\n" + + " }]\n" + + " }"; + + OpOperation opOperation = formatter.parseOperation(msg); + String hash = JSON_MSG_TYPE + ":" + + SecUtils.calculateHashWithAlgo(SecUtils.HASH_SHA256, null, + formatter.opToJsonNoHash(opOperation)); + + byte[] hashBytes = SecUtils.getHashBytes(hash); + String signatureTxt = SecUtils.signMessageWithKeyBase64(kp, hashBytes, SecUtils.SIG_ALGO_ECDSA, null); + System.out.println(formatter.opToJsonNoHash(opOperation)); + System.out.println(hash); + System.out.println(signatureTxt); + } + + public static EncodedKeySpec decodeKey(String key) { + if (key.startsWith(KEY_BASE64 + ":")) { + key = key.substring(KEY_BASE64.length() + 1); + int s = key.indexOf(':'); + if (s == -1) { + throw new IllegalArgumentException(String.format("Key doesn't contain algorithm of hashing to verify")); + } + return getKeySpecByFormat(key.substring(0, s), + android.util.Base64.decode(key.substring(s + 1), android.util.Base64.DEFAULT)); + } + throw new IllegalArgumentException(String.format("Key doesn't contain algorithm of hashing to verify")); + } + + public static String encodeKey(String algo, PublicKey pk) { + if (algo.equals(KEY_BASE64)) { + return SecUtils.KEY_BASE64 + ":" + pk.getFormat() + ":" + encodeBase64(pk.getEncoded()); + } + throw new UnsupportedOperationException("Algorithm is not supported: " + algo); + } + + public static String encodeKey(String algo, PrivateKey pk) { + if (algo.equals(KEY_BASE64)) { + return SecUtils.KEY_BASE64 + ":" + pk.getFormat() + ":" + encodeBase64(pk.getEncoded()); + } + throw new UnsupportedOperationException("Algorithm is not supported: " + algo); + } + + public static EncodedKeySpec getKeySpecByFormat(String format, byte[] data) { + switch (format) { + case "PKCS#8": + return new PKCS8EncodedKeySpec(data); + case "X.509": + return new X509EncodedKeySpec(data); + } + throw new IllegalArgumentException(format); + } + + public static String encodeBase64(byte[] data) { + return new String(android.util.Base64.decode(data, android.util.Base64.DEFAULT)); + } + + public static boolean validateKeyPair(String algo, PrivateKey privateKey, PublicKey publicKey) + throws FailedVerificationException { + if (!algo.equals(ALGO_EC)) { + throw new FailedVerificationException("Algorithm is not supported: " + algo); + } + // create a challenge + byte[] challenge = new byte[512]; + ThreadLocalRandom.current().nextBytes(challenge); + + try { + // sign using the private key + Signature sig = Signature.getInstance(SIG_ALGO_SHA1_EC); + sig.initSign(privateKey); + sig.update(challenge); + byte[] signature = sig.sign(); + + // verify signature using the public key + sig.initVerify(publicKey); + sig.update(challenge); + + boolean keyPairMatches = sig.verify(signature); + return keyPairMatches; + } catch (InvalidKeyException e) { + throw new FailedVerificationException(e); + } catch (NoSuchAlgorithmException e) { + throw new FailedVerificationException(e); + } catch (SignatureException e) { + throw new FailedVerificationException(e); + } + } + + public static KeyPair getKeyPair(String algo, String prKey, String pbKey) throws FailedVerificationException { + try { + KeyFactory keyFactory = KeyFactory.getInstance(algo); + PublicKey pb = null; + PrivateKey pr = null; + if (pbKey != null) { + pb = keyFactory.generatePublic(decodeKey(pbKey)); + } + if (prKey != null) { + pr = keyFactory.generatePrivate(decodeKey(prKey)); + } + return new KeyPair(pb, pr); + } catch (NoSuchAlgorithmException e) { + throw new FailedVerificationException(e); + } catch (InvalidKeySpecException e) { + throw new FailedVerificationException(e); + } + } + + public static KeyPair generateKeyPairFromPassword(String algo, String keygenMethod, String salt, String pwd) + throws FailedVerificationException { + if (algo.equals(ALGO_EC)) { + return generateECKeyPairFromPassword(keygenMethod, salt, pwd); + } + throw new UnsupportedOperationException("Unsupported algo keygen method: " + algo); + } + + public static KeyPair generateECKeyPairFromPassword(String keygenMethod, String salt, String pwd) + throws FailedVerificationException { + if (keygenMethod.equals(KEYGEN_PWD_METHOD_1)) { + return generateEC256K1KeyPairFromPassword(salt, pwd); + } + throw new UnsupportedOperationException("Unsupported keygen method: " + keygenMethod); + } + + // "EC:secp256k1:scrypt(salt,N:17,r:8,p:1,len:256)" algorithm - EC256K1_S17R8 + public static KeyPair generateEC256K1KeyPairFromPassword(String salt, String pwd) + throws FailedVerificationException { + try { + KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_EC); + ECGenParameterSpec ecSpec = new ECGenParameterSpec(EC_256SPEC_K1); + if (pwd.length() < 10) { + throw new IllegalArgumentException("Less than 10 characters produces only 50 bit entropy"); + } + byte[] bytes = pwd.getBytes("UTF-8"); + //byte[] scrypt = SCrypt.generate(bytes, salt.getBytes("UTF-8"), 1 << 17, 8, 1, 256); + //kpg.initialize(ecSpec, new FixedSecureRandom(scrypt)); + return kpg.genKeyPair(); + } catch (NoSuchAlgorithmException e) { + throw new FailedVerificationException(e); + } catch (UnsupportedEncodingException e) { + throw new FailedVerificationException(e); + } /* catch (InvalidAlgorithmParameterException e) { + throw new FailedVerificationException(e); + }*/ + } + + public static KeyPair generateRandomEC256K1KeyPair() throws FailedVerificationException { + try { + KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_EC); + ECGenParameterSpec ecSpec = new ECGenParameterSpec(EC_256SPEC_K1); + kpg.initialize(ecSpec); + return kpg.genKeyPair(); + } catch (NoSuchAlgorithmException e) { + throw new FailedVerificationException(e); + } catch (InvalidAlgorithmParameterException e) { + throw new FailedVerificationException(e); + } + } + + public static String signMessageWithKeyBase64(KeyPair keyPair, byte[] msg, String signAlgo, ByteArrayOutputStream out) { + byte[] sigBytes = signMessageWithKey(keyPair, msg, signAlgo); + if(out != null) { + try { + out.write(sigBytes); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + String signature = new String(android.util.Base64.decode(sigBytes, android.util.Base64.DEFAULT)); + return signAlgo + ":" + DECODE_BASE64 + ":" + signature; + } + + public static byte[] signMessageWithKey(KeyPair keyPair, byte[] msg, String signAlgo) { + try { + Signature sig = Signature.getInstance(getInternalSigAlgo(signAlgo)); + sig.initSign(keyPair.getPrivate()); + sig.update(msg); + byte[] signatureBytes = sig.sign(); + return signatureBytes; + } catch (NoSuchAlgorithmException e) { + //throw new FailedVerificationException(e); + } catch (InvalidKeyException e) { + //throw new FailedVerificationException(e); + } catch (SignatureException e) { + //throw new FailedVerificationException(e); + } + return new byte[0]; + } + + public static boolean validateSignature(KeyPair keyPair, byte[] msg, String sig) { + if(sig == null || keyPair == null) { + return false; + } + int ind = sig.indexOf(':'); + String sigAlgo = sig.substring(0, ind); + return validateSignature(keyPair, msg, sigAlgo, decodeSignature(sig.substring(ind + 1))); + } + + public static boolean validateSignature(KeyPair keyPair, byte[] msg, String sigAlgo, byte[] signature) { + if (keyPair == null) { + return false; + } + try { + Signature sig = Signature.getInstance(getInternalSigAlgo(sigAlgo)); + sig.initVerify(keyPair.getPublic()); + sig.update(msg); + return sig.verify(signature); + } catch (NoSuchAlgorithmException e) { + //throw new FailedVerificationException(e); + } catch (InvalidKeyException e) { + //throw new FailedVerificationException(e); + } catch (SignatureException e) { + //throw new FailedVerificationException(e); + } + return false; + } + + private static String getInternalSigAlgo(String sigAlgo) { + return sigAlgo.equals(SIG_ALGO_ECDSA)? SIG_ALGO_NONE_EC : sigAlgo; + } + + + public static byte[] calculateHash(String algo, byte[] b1, byte[] b2) { + byte[] m = mergeTwoArrays(b1, b2); + if (algo.equals(HASH_SHA256)) { + return DigestUtils.sha256(m); + } else if (algo.equals(HASH_SHA1)) { + return DigestUtils.sha1(m); + } + throw new UnsupportedOperationException(); + } + + public static byte[] mergeTwoArrays(byte[] b1, byte[] b2) { + byte[] m = b1 == null ? b2 : b1; + if(b2 != null && b1 != null) { + m = new byte[b1.length + b2.length]; + System.arraycopy(b1, 0, m, 0, b1.length); + System.arraycopy(b2, 0, m, b1.length, b2.length); + } + return m; + } + + public static String calculateHashWithAlgo(String algo, String salt, String msg) { + try { + String hex = Hex.encodeHexString(calculateHash(algo, salt == null ? null : salt.getBytes("UTF-8"), + msg == null ? null : msg.getBytes("UTF-8"))); + return algo + ":" + hex; + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + } + + public static String calculateHashWithAlgo(String algo, byte[] bts) { + byte[] hash = calculateHash(algo, bts, null); + return formatHashWithAlgo(algo, hash); + } + + public static String formatHashWithAlgo(String algo, byte[] hash) { + String hex = Hex.encodeHexString(hash); + return algo + ":" + hex; + } + + public static byte[] getHashBytes(String msg) { + if(msg == null || msg.length() == 0) { + // special case for empty hash + return new byte[0]; + } + int i = msg.lastIndexOf(':'); + String s = i >= 0 ? msg.substring(i + 1) : msg; + try { + return Hex.decodeHex(s); + } catch (DecoderException e) { + throw new IllegalArgumentException(e); + } + } + + + public static boolean validateHash(String hash, String salt, String msg) { + int s = hash.indexOf(":"); + if (s == -1) { + throw new IllegalArgumentException(String.format("Hash %s doesn't contain algorithm of hashing to verify", + s)); + } + String v = calculateHashWithAlgo(hash.substring(0, s), salt, msg); + return hash.equals(v); + } + + public static byte[] decodeSignature(String digest) { + try { + int indexOf = digest.indexOf(DECODE_BASE64 + ":"); + if (indexOf != -1) { +// return Base64.getDecoder().decode(digest.substring(indexOf + DECODE_BASE64.length() + 1). +// getBytes("UTF-8")); + return android.util.Base64.decode(digest.substring(indexOf + DECODE_BASE64.length() + 1) + .getBytes("UTF-8"), android.util.Base64.DEFAULT); + } + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + throw new IllegalArgumentException("Unknown format for signature " + digest); + } + + public static String hexify(byte[] bytes) { + if(bytes == null || bytes.length == 0) { + return ""; + } + return Hex.encodeHexString(bytes); + + } + + + +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpObject.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpObject.java new file mode 100644 index 0000000000..20350f6572 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpObject.java @@ -0,0 +1,530 @@ +package net.osmand.plus.osmedit.utils.ops; + +import com.google.gson.*; +import net.osmand.plus.osmedit.utils.util.JsonObjectUtils; +import net.osmand.plus.osmedit.utils.util.OUtils; + +import java.lang.reflect.Type; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +public class OpObject { + + public static final String F_NAME = "name"; + public static final String F_ID = "id"; + public static final String F_COMMENT = "comment"; + public static final String TYPE_OP = "sys.op"; + public static final String TYPE_BLOCK = "sys.block"; + public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + // transient info about validation timing etc + public static final String F_EVAL = "eval"; + public static final String F_VALIDATION = "validation"; + public static final String F_TIMESTAMP_ADDED = "timestamp"; + public static final String F_PARENT_TYPE = "parentType"; + public static final String F_PARENT_HASH = "parentHash"; + public static final String F_CHANGE = "change"; + public static final String F_CURRENT = "current"; + // voting + public static final String F_OP = "op"; + public static final String F_STATE = "state"; + public static final String F_OPEN = "open"; + public static final String F_FINAL = "final"; + public static final String F_VOTE = "vote"; + public static final String F_VOTES = "votes"; + public static final String F_SUBMITTED_OP_HASH = "submittedOpHash"; + public static final String F_USER = "user"; + + public static final OpObject NULL = new OpObject(true); + + public static SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + static { + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + protected Map fields = new TreeMap<>(); + protected transient Map cacheFields; + protected boolean isImmutable; + + protected transient String parentType; + protected transient String parentHash; + protected transient boolean deleted; + + + public OpObject() {} + + public OpObject(boolean deleted) { + this.deleted = deleted; + } + + public OpObject(OpObject cp) { + this(cp, false); + } + + public OpObject(OpObject cp, boolean copyCacheFields) { + createOpObjectCopy(cp, copyCacheFields); + } + + @SuppressWarnings("unchecked") + private OpObject createOpObjectCopy(OpObject opObject, boolean copyCacheFields) { + this.parentType = opObject.parentType; + this.parentHash = opObject.parentHash; + this.deleted = opObject.deleted; + this.fields = (Map) copyingObjects(opObject.fields, copyCacheFields); + if (opObject.cacheFields != null && copyCacheFields) { + this.cacheFields = (Map) copyingObjects(opObject.cacheFields, copyCacheFields); + } + this.isImmutable = false; + + return this; + } + + public boolean isDeleted() { + return deleted; + } + + @SuppressWarnings("unchecked") + private Object copyingObjects(Object object, boolean copyCacheFields) { + if (object instanceof Number) { + return (Number) object; + } else if (object instanceof String) { + return (String) object; + } else if (object instanceof Boolean) { + return (Boolean) object; + } else if (object instanceof List) { + List copy = new ArrayList<>(); + List list = (List) object; + for (Object o : list) { + copy.add(copyingObjects(o, copyCacheFields)); + } + return copy; + } else if (object instanceof Map) { + Map copy = new LinkedHashMap<>(); + Map map = (Map) object; + for (Object o : map.keySet()) { + copy.put(o, copyingObjects(map.get(o), copyCacheFields)); + } + return copy; + } else if (object instanceof OpObject) { + return new OpObject((OpObject) object); + } else { + throw new UnsupportedOperationException("Type of object is not supported"); + } + } + + public void setParentOp(OpOperation op) { + setParentOp(op.type, op.getRawHash()); + } + + public void setParentOp(String parentType, String parentHash) { + this.parentType = parentType; + this.parentHash = parentHash; + } + + public String getParentHash() { + return parentHash; + } + + public String getParentType() { + return parentType; + } + + public List getId() { + return getStringList(F_ID); + } + + public void setId(String id) { + addOrSetStringValue(F_ID, id);; + } + + public boolean isImmutable() { + return isImmutable; + } + + public OpObject makeImmutable() { + isImmutable = true; + return this; + } + + public Object getFieldByExpr(String field) { + if (field.contains(".") || field.contains("[") || field.contains("]")) { + return JsonObjectUtils.getField(this.fields, generateFieldSequence(field)); + } + + return fields.get(field); + } + + + /** + * generateFieldSequence("a") - [a] + * generateFieldSequence("a.b") - [a, b] + * generateFieldSequence("a.b.c.de") - [a, b, c, de] + * generateFieldSequence("a.bwerq.c") - [a, bwerq, c] + * generateFieldSequence("a.bwerq...c") - [a, bwerq, c] + * generateFieldSequence("a.bwereq..c..") - [a, bwerq, c] + * generateFieldSequence("a.{b}") - [a, b] + * generateFieldSequence("a.{b.c.de}") - [a, b.c.de] + * generateFieldSequence("a.{b.c.de}") - [a, b.c.de] + * generateFieldSequence("a.{b{}}") - [a, b{}] + * generateFieldSequence("a.{b{}d.q}") - [a, b{}d.q] + */ + private static List generateFieldSequence(String field) { + int STATE_OPEN_BRACE = 1; + int STATE_OPEN = 0; + int state = STATE_OPEN; + int start = 0; + List l = new ArrayList(); + for(int i = 0; i < field.length(); i++) { + boolean split = false; + if (i == field.length() - 1) { + if (state == STATE_OPEN_BRACE) { + if(field.charAt(i) == '}') { + split = true; + } else { + throw new IllegalArgumentException("Illegal field expression: " + field); + } + } else { + if(field.charAt(i) != '.') { + i++; + } + split = true; + } + } else { + if (field.charAt(i) == '.' && state == STATE_OPEN) { + split = true; + } else if (field.charAt(i) == '}' && field.charAt(i + 1) == '.' && state == STATE_OPEN_BRACE) { + split = true; + } else if (field.charAt(i) == '{' && state == STATE_OPEN) { + if(start != i) { + throw new IllegalArgumentException("Illegal field expression (wrap {} is necessary): " + field); + } + state = STATE_OPEN_BRACE; + start = i + 1; + } + } + if(split) { + if (i != start) { + l.add(field.substring(start, i)); + } + start = i + 1; + state = STATE_OPEN; + } + } + return l; + } + + public void setFieldByExpr(String field, Object object) { + if (field.contains(".") || field.contains("[") || field.contains("]")) { + List fieldSequence = generateFieldSequence(field); + if (object == null) { + JsonObjectUtils.deleteField(this.fields, fieldSequence); + } else { + JsonObjectUtils.setField(this.fields, fieldSequence, object); + } + } else if (object == null) { + fields.remove(field); + } else { + fields.put(field, object); + } + } + + + public Object getCacheObject(String f) { + if(cacheFields == null) { + return null; + } + return cacheFields.get(f); + } + + public void putCacheObject(String f, Object o) { + if (isImmutable()) { + if (cacheFields == null) { + cacheFields = new ConcurrentHashMap(); + } + cacheFields.put(f, o); + } + } + + public void setId(String id, String id2) { + List list = new ArrayList(); + list.add(id); + list.add(id2); + putObjectValue(F_ID, list); + } + + public String getName() { + return getStringValue(F_NAME); + } + + public String getComment() { + return getStringValue(F_COMMENT); + } + + public Map getRawOtherFields() { + return fields; + } + + @SuppressWarnings("unchecked") + public Map getStringMap(String field) { + return (Map) fields.get(field); + } + + @SuppressWarnings("unchecked") + public Map> getMapStringList(String field) { + return (Map>) fields.get(field); + } + + @SuppressWarnings("unchecked") + public List> getListStringMap(String field) { + return (List>) fields.get(field); + } + + @SuppressWarnings("unchecked") + public List> getListStringObjMap(String field) { + return (List>) fields.get(field); + } + + @SuppressWarnings("unchecked") + public Map getStringObjMap(String field) { + return (Map) fields.get(field); + } + + @SuppressWarnings("unchecked") + public T getField(T def, String... fields) { + Map p = this.fields; + for(int i = 0; i < fields.length - 1 ; i++) { + p = (Map) p.get(fields[i]); + if(p == null) { + return def; + } + } + T res = (T) p.get(fields[fields.length - 1]); + if(res == null) { + return def; + } + return res; + } + + @SuppressWarnings("unchecked") + public Map, Object> getStringListObjMap(String field) { + return (Map, Object>) fields.get(field); + } + + + public long getDate(String field) { + String date = getStringValue(field); + if(OUtils.isEmpty(date)) { + return 0; + } + try { + return dateFormat.parse(date).getTime(); + } catch (ParseException e) { + return 0; + } + } + + + public void setDate(String field, long time) { + putStringValue(field, dateFormat.format(new Date(time))); + } + + public Number getNumberValue(String field) { + return (Number) fields.get(field); + } + + public int getIntValue(String key, int def) { + Number o = getNumberValue(key); + return o == null ? def : o.intValue(); + } + + public long getLongValue(String key, long def) { + Number o = getNumberValue(key); + return o == null ? def : o.longValue(); + } + + public String getStringValue(String field) { + Object o = fields.get(field); + if (o instanceof String || o == null) { + return (String) o; + } + return o.toString(); + } + + @SuppressWarnings("unchecked") + public List getStringList(String field) { + // cast to list if it is single value + Object o = fields.get(field); + if(o == null || o.toString().isEmpty()) { + return Collections.emptyList(); + } + if(o instanceof String) { + return Collections.singletonList(o.toString()); + } + return (List) o; + } + + public Object getObjectValue(String field) { + return fields.get(field); + } + + public void putStringValue(String key, String value) { + checkNotImmutable(); + if(value == null) { + fields.remove(key); + } else { + fields.put(key, value); + } + } + + /** + * Operates as a single value if cardinality is less than 1 + * or as a list of values if it stores > 1 value + * @param key + * @param value + */ + @SuppressWarnings("unchecked") + public void addOrSetStringValue(String key, String value) { + checkNotImmutable(); + Object o = fields.get(key); + if(o == null) { + fields.put(key, value); + } else if(o instanceof List) { + ((List) o).add(value); + } else { + List list = new ArrayList(); + list.add(o.toString()); + list.add(value); + fields.put(key, list); + } + } + + @SuppressWarnings("unchecked") + public Map getChangedEditFields() { + return (Map) fields.get(F_CHANGE); + } + + @SuppressWarnings("unchecked") + public Map getCurrentEditFields() { + return (Map) fields.get(F_CURRENT); + } + + public void putObjectValue(String key, Object value) { + checkNotImmutable(); + if(value == null) { + fields.remove(key); + } else { + fields.put(key, value); + } + } + + public void checkNotImmutable() { + if(isImmutable) { + throw new IllegalStateException("Object is immutable"); + } + + } + + public void checkImmutable() { + if(!isImmutable) { + throw new IllegalStateException("Object is mutable"); + } + } + + public Object remove(String key) { + checkNotImmutable(); + return fields.remove(key); + } + + public Map getMixedFieldsAndCacheMap() { + TreeMap mp = new TreeMap<>(fields); + if(cacheFields != null || parentType != null || parentHash != null) { + TreeMap eval = new TreeMap(); + + if(parentType != null) { + eval.put(F_PARENT_TYPE, parentType); + } + if(parentHash != null) { + eval.put(F_PARENT_HASH, parentHash); + } + if (cacheFields != null) { + Iterator> it = cacheFields.entrySet().iterator(); + while (it.hasNext()) { + Entry e = it.next(); + Object v = e.getValue(); + if (v instanceof Map || v instanceof String || v instanceof Number) { + eval.put(e.getKey(), v); + } + } + } + if(eval.size() > 0) { + mp.put(F_EVAL, eval); + } + } + return mp; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((fields == null) ? 0 : fields.hashCode()); + return result; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + fields + "]"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + OpObject other = (OpObject) obj; + if (fields == null) { + if (other.fields != null) + return false; + } else if (!fields.equals(other.fields)) + return false; + return true; + } + + public static class OpObjectAdapter implements JsonDeserializer, + JsonSerializer { + + private boolean fullOutput; + + public OpObjectAdapter(boolean fullOutput) { + this.fullOutput = fullOutput; + } + + @Override + public OpObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + OpObject bn = new OpObject(); + bn.fields = context.deserialize(json, TreeMap.class); + // remove cache + bn.fields.remove(F_EVAL); + return bn; + } + + @Override + public JsonElement serialize(OpObject src, Type typeOfSrc, JsonSerializationContext context) { + return context.serialize(fullOutput ? src.getMixedFieldsAndCacheMap() : src.fields); + } + + + } + + + + + + +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpOperation.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpOperation.java new file mode 100644 index 0000000000..ad1c9571ae --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/OpOperation.java @@ -0,0 +1,281 @@ +package net.osmand.plus.osmedit.utils.ops; + +import com.google.gson.*; + +import java.lang.reflect.Type; +import java.util.*; + +public class OpOperation extends OpObject { + + public static final String F_TYPE = "type"; + public static final String F_SIGNED_BY = "signed_by"; + public static final String F_HASH = "hash"; + + public static final String F_SIGNATURE = "signature"; + + public static final String F_REF = "ref"; + public static final String F_CREATE = "create"; + public static final String F_DELETE = "delete"; + public static final String F_EDIT = "edit"; + + public static final String F_NAME = "name"; + public static final String F_COMMENT = "comment"; + + private List createdObjects = new LinkedList(); + private List editedObjects = new LinkedList(); + protected String type; + + public OpOperation() { + } + + public OpOperation(OpOperation cp, boolean copyCacheFields) { + super(cp, copyCacheFields); + this.type = cp.type; + for(OpObject o : cp.createdObjects) { + this.createdObjects.add(new OpObject(o, copyCacheFields)); + } + for(OpObject o : cp.editedObjects) { + this.editedObjects.add(new OpObject(o, copyCacheFields)); + } + } + + public String getOperationType() { + return type; + } + + public void setType(String name) { + checkNotImmutable(); + type = name; + updateObjectsRef(); + } + + protected void updateObjectsRef() { + for(OpObject o : createdObjects) { + o.setParentOp(this); + } + for(OpObject o : editedObjects) { + o.setParentOp(this); + } + } + + public String getType() { + return type; + } + + public OpOperation makeImmutable() { + isImmutable = true; + for(OpObject o : createdObjects) { + o.makeImmutable(); + } + for(OpObject o : editedObjects) { + o.makeImmutable(); + } + return this; + } + + public void setSignedBy(String value) { + putStringValue(F_SIGNED_BY, value); + } + + public void addOtherSignedBy(String value) { + super.addOrSetStringValue(F_SIGNED_BY, value); + } + + public List getSignedBy() { + return getStringList(F_SIGNED_BY); + } + + public String getHash() { + return getStringValue(F_HASH); + } + + public String getRawHash() { + String rw = getStringValue(F_HASH); + // drop algorithm and everything else + if(rw != null) { + rw = rw.substring(rw.lastIndexOf(':') + 1); + } + return rw; + } + + public List getSignatureList() { + return getStringList(F_SIGNATURE); + } + + public Map> getRef() { + return getMapStringList(F_REF); + } + + @SuppressWarnings("unchecked") + public List> getDeleted() { + List> l = (List>) fields.get(F_DELETE); + if(l == null) { + return Collections.emptyList(); + } + return l; + } + + public boolean hasDeleted() { + return getDeleted().size() > 0; + } + + public void addDeleted(List id) { + if(!fields.containsKey(F_DELETE)) { + ArrayList> lst = new ArrayList<>(); + lst.add(id); + putObjectValue(F_DELETE, lst); + } else { + getDeleted().add(id); + } + } + + public List getCreated() { + return createdObjects; + } + + public void addCreated(OpObject o) { + checkNotImmutable(); + createdObjects.add(o); + if(type != null) { + o.setParentOp(this); + } + } + + public boolean hasCreated() { + return createdObjects.size() > 0; + } + + public void addEdited(OpObject o) { + checkNotImmutable(); + editedObjects.add(o); + if (type != null) { + o.setParentOp(this); + } + } + + public List getEdited() { + return editedObjects; + } + + public boolean hasEdited() { + return editedObjects.size() > 0; + } + + + public String getName() { + return getStringValue(F_NAME); + } + + public String getComment() { + return getStringValue(F_COMMENT); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((createdObjects == null) ? 0 : createdObjects.hashCode()); + result = prime * result + ((editedObjects == null) ? 0 : editedObjects.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + OpOperation other = (OpOperation) obj; + if (createdObjects == null) { + if (other.createdObjects != null) + return false; + } else if (!createdObjects.equals(other.createdObjects)) + return false; + if (editedObjects == null) { + if (other.editedObjects != null) + return false; + } else if (!editedObjects.equals(other.editedObjects)) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + return true; + } + + + + public static class OpOperationBeanAdapter implements JsonDeserializer, + JsonSerializer { + + // plain serialization to calculate hash + private boolean excludeHashAndSignature; + private boolean fullOutput; + + public OpOperationBeanAdapter(boolean fullOutput, boolean excludeHashAndSignature) { + this.excludeHashAndSignature = excludeHashAndSignature; + this.fullOutput = fullOutput; + } + + public OpOperationBeanAdapter(boolean fullOutput) { + this.fullOutput = fullOutput; + this.excludeHashAndSignature = false; + } + + @Override + public OpOperation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + JsonObject jsonObj = json.getAsJsonObject(); + OpOperation op = new OpOperation(); + JsonElement tp = jsonObj.remove(F_TYPE); + if(tp != null) { + String opType = tp.getAsString(); + op.type = opType; + } else { + op.type = ""; + } + JsonElement createdObjs = jsonObj.remove(F_CREATE); + if(createdObjs != null) { + JsonArray ar = createdObjs.getAsJsonArray(); + for(int i = 0; i < ar.size(); i++) { + //op.addCreated(context.deserialize(ar.get(i), OpObject.class)); + } + } + + JsonElement editedObjs = jsonObj.remove(F_EDIT); + if (editedObjs != null) { + for (JsonElement editElem : editedObjs.getAsJsonArray()) { + //op.addEdited(context.deserialize(editElem, OpObject.class)); + } + } + + jsonObj.remove(F_EVAL); + op.fields = context.deserialize(jsonObj, TreeMap.class); + return op; + } + + @Override + public JsonElement serialize(OpOperation src, Type typeOfSrc, JsonSerializationContext context) { + TreeMap tm = new TreeMap<>(fullOutput ? src.getMixedFieldsAndCacheMap() : src.fields); + if(excludeHashAndSignature) { + tm.remove(F_SIGNATURE); + tm.remove(F_HASH); + } + tm.put(F_TYPE, src.type); + + if (src.hasEdited()) { + tm.put(F_EDIT, context.serialize(src.editedObjects)); + } + + if(src.hasCreated()) { + tm.put(F_CREATE, context.serialize(src.createdObjects)); + } + return context.serialize(tm); + } + } + +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/PerformanceMetrics.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/PerformanceMetrics.java new file mode 100644 index 0000000000..a6abf266d6 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/ops/PerformanceMetrics.java @@ -0,0 +1,171 @@ +package net.osmand.plus.osmedit.utils.ops; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class PerformanceMetrics { + private static final PerformanceMetrics inst = new PerformanceMetrics(); + public static final int METRICS_COUNT = 2; + public static PerformanceMetrics i() { + return inst; + } + private final Map metrics = new ConcurrentHashMap(); + private final AtomicInteger ids = new AtomicInteger(); + private final PerformanceMetric DISABLED = new PerformanceMetric(-1, ""); + private boolean enabled = true; + private PerformanceMetric overhead; + + private PerformanceMetrics() { + overhead = getByKey("_overhead"); + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Map getMetrics() { + return metrics; + } + + public PerformanceMetric getMetric(String prefix, String key) { + if(!enabled) { + return DISABLED; + } + return getMetric(prefix + "." + key); + } + + public void reset(int c) { + for(PerformanceMetric p : metrics.values()) { + p.reset(c); + } + } + + + public PerformanceMetric getMetric(String key) { + if(!enabled) { + return DISABLED; + } + long s = System.nanoTime(); + PerformanceMetric pm = getByKey(key); + overhead.capture(System.nanoTime() - s); + return pm; + } + + private PerformanceMetric getByKey(String key) { + PerformanceMetric pm = metrics.get(key); + if(pm == null) { + pm = new PerformanceMetric(ids.incrementAndGet(), key); + metrics.put(key, pm); + } + return pm; + } + + + public final class PerformanceMetric { + final String name; + final int id; + String description; + AtomicInteger invocations = new AtomicInteger(); + AtomicLong totalDuration = new AtomicLong(); + AtomicInteger invocationsA = new AtomicInteger(); + AtomicLong totalDurationA = new AtomicLong(); + AtomicInteger invocationsB = new AtomicInteger(); + AtomicLong totalDurationB = new AtomicLong(); + + + + private PerformanceMetric(int id, String name) { + this.id = id; + this.name = name; + } + + public Metric start() { + if(id == -1) { + return Metric.EMPTY; + } + return new Metric(this); + } + + public String getName() { + return name; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public void reset(int c) { + if(c == 1) { + invocationsA.set(0); + totalDurationA.set(0); + } else if(c == 2) { + totalDurationB.set(0); + invocationsB.set(0); + } + } + + public int getInvocations(int c) { + if(c == 1) { + return invocationsA.get(); + } else if(c == 2) { + return invocationsB.get(); + } + return invocations.get(); + } + + public long getDuration(int c) { + if(c == 1) { + return totalDurationA.get(); + } else if(c == 2) { + return totalDurationB.get(); + } + return totalDuration.get(); + } + + public int getId() { + return id; + } + + long capture(long d) { + invocations.incrementAndGet(); + totalDuration.addAndGet(d); + invocationsA.incrementAndGet(); + totalDurationA.addAndGet(d); + invocationsB.incrementAndGet(); + totalDurationB.addAndGet(d); + return d; + } + } + + public static final class Metric { + long start; + PerformanceMetric m; + boolean e; + public static final Metric EMPTY = new Metric(true); + private Metric(PerformanceMetric m) { + start = System.nanoTime(); + this.m = m; + } + + private Metric(boolean empty) { + e = empty; + } + + public long capture() { + if(e) { + return 0; + } + return m.capture(System.nanoTime() - start); + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/DBConstants.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/DBConstants.java new file mode 100644 index 0000000000..fb769b10c5 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/DBConstants.java @@ -0,0 +1,25 @@ +package net.osmand.plus.osmedit.utils.util; + +public interface DBConstants { + + public static final String SCHEMA_NAME = "public"; + + // Tables + public static final String BLOCK_TABLE = "blocks"; + + public static final String LOGINS_TABLE = "logins"; + + public static final String USERS_TABLE = "users"; + + public static final String QUEUE_TABLE = "queue"; + + public static final String ROLES_TABLE = "roles"; + + public static final String TABLES_TABLE = "tables"; + + public static final String OP_DEFINITIONS_TABLE = "op_definitions"; + + public static final String EXECUTED_OPERATIONS_TABLE = "operations"; + + +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/JsonObjectUtils.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/JsonObjectUtils.java new file mode 100644 index 0000000000..e8ccc187f1 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/JsonObjectUtils.java @@ -0,0 +1,275 @@ +package net.osmand.plus.osmedit.utils.util; + +import java.util.*; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Class uses for work with Json Object represent as Map. + */ +public class JsonObjectUtils { + + + private static final int GET_OPERATION = 0; + private static final int SET_OPERATION = 1; + private static final int DELETE_OPERATION = 2; + protected static final Log LOGGER = LogFactory.getLog(JsonObjectUtils.class); + + private static class OperationAccess { + private final int operation; + private final Object value; + + private OperationAccess(int op, Object v) { + this.operation = op; + this.value = v; + } + + } + + /** + * Retrieve value from jsonMap by field sequence. + * @param jsonMap source json object deserialized in map + * @param fieldSequence Sequence to field value. + * Example: person.car.number have to be ["person", "car[2]", "number"] + * @return Field value + */ + public static Object getField(Map jsonMap, String[] fieldSequence) { + return accessField(jsonMap, fieldSequence, new OperationAccess(GET_OPERATION, null)); + } + + /** + * Set value to json field (path to field presented as sequence of string) + * + * @param jsonMap source json object deserialized in map + * @param fieldSequence Sequence to field value. + * * Example: person.car.number have to be ["person", "car[2]", "number"] + * @param field field value + * @return + */ + public static Object setField(Map jsonMap, List fieldSequence, Object field) { + return setField(jsonMap, fieldSequence.toArray(new String[fieldSequence.size()]), field); + } + + /** + * Set value to json field (path to field presented as sequence of string) + * + * @param jsonObject source json object deserialized in map + * @param fieldSequence Sequence to field value. + * * Example: person.car.number have to be ["person", "car[2]", "number"] + * @param field field value + * @return + */ + public static Object setField(Map jsonObject, String[] fieldSequence, Object field) { + return accessField(jsonObject, fieldSequence, new OperationAccess(SET_OPERATION, field)); + } + + + /** + * Retrieve value from jsonMap by field sequence. + * + * @param jsonObject source json object deserialized in map + * @param fieldSequence Sequence to field value. + * Example: person.car.number have to be ["person", "car[2]", "number"] + * @return Field value + */ + public static Object getField(Map jsonObject, List fieldSequence) { + return getField(jsonObject, fieldSequence.toArray(new String[fieldSequence.size()])); + } + + /** + * Delete field value from json Map (field path presented as sequence of string) + * + * @param jsonMap source json object deserialized in map + * @param fieldSequence Sequence to field value. + * Example: person.car.number have to be ["person", "car[2]", "number"] + * @return + */ + public static Object deleteField(Map jsonMap, List fieldSequence) { + return accessField(jsonMap, fieldSequence.toArray(new String[fieldSequence.size()]), new OperationAccess(DELETE_OPERATION, null)); + } + + + @SuppressWarnings("unchecked") + private static Object accessField(Map jsonObject, String[] fieldSequence, OperationAccess op) { + if (fieldSequence == null || fieldSequence.length == 0) { + throw new IllegalArgumentException("Field sequence is empty. Set value to root not possible."); + } + String fieldName = null; + Map jsonObjLocal = jsonObject; + List jsonListLocal = null; + int indexToAccess = -1; + for(int i = 0; i < fieldSequence.length; i++) { + boolean last = i == fieldSequence.length - 1; + fieldName = fieldSequence[i]; + int indOpArray = -1; + for(int ic = 0; ic < fieldName.length(); ) { + if(ic > 0 && (fieldName.charAt(ic) == '[' || fieldName.charAt(ic) == ']') && + fieldName.charAt(ic - 1) == '\\') { + // replace '\[' with '[' + fieldName = fieldName.substring(0, ic - 1) + fieldName.substring(ic); + } else if(fieldName.charAt(ic) == '[') { + indOpArray = ic; + break; + } else { + ic++; + } + } + jsonListLocal = null; // reset + if(indOpArray == -1) { + if(!last) { + Map fieldAccess = (Map) jsonObjLocal.get(fieldName); + if(fieldAccess == null) { + if(op.operation == GET_OPERATION) { + // don't modify during get operation + return null; + } + Map newJsonMap = new TreeMap<>(); + jsonObjLocal.put(fieldName, newJsonMap); + jsonObjLocal = newJsonMap; + } else { + jsonObjLocal = fieldAccess; + } + } + } else { + String arrayFieldName = fieldName.substring(0, indOpArray); + if(arrayFieldName.contains("]")) { + throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i])); + } + jsonListLocal = (List) jsonObjLocal.get(arrayFieldName); + if (jsonListLocal == null) { + if (op.operation == GET_OPERATION) { + // don't modify during get operation + return null; + } + jsonListLocal = new ArrayList(); + jsonObjLocal.put(arrayFieldName, jsonListLocal); + } + while (indOpArray != -1) { + fieldName = fieldName.substring(indOpArray + 1); + int indClArray = fieldName.indexOf("]"); + if (indClArray == -1) { + throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i])); + } + if(indClArray == fieldName.length() - 1) { + indOpArray = -1; + } else if(fieldName.charAt(indClArray + 1) == '[') { + indOpArray = indClArray + 1; + } else { + throw new IllegalArgumentException(String.format("Illegal field array modifier %s", fieldSequence[i])); + } + int index = Integer.parseInt(fieldName.substring(0, indClArray)); + if (last && indOpArray == -1) { + indexToAccess = index; + } else { + Object obj = null; + if (index < jsonListLocal.size() && index >= 0) { + obj = jsonListLocal.get(index); + } else if (op.operation == SET_OPERATION && (index == -1 || index == jsonListLocal.size())) { + index = jsonListLocal.size(); + jsonListLocal.add(null); + } else { + throw new IllegalArgumentException( + String.format("Illegal access to array at position %d", index)); + } + + if (obj == null) { + if (op.operation == GET_OPERATION) { + // don't modify during get operation + return null; + } + if (indOpArray == -1) { + obj = new TreeMap<>(); + } else { + obj = new ArrayList(); + } + jsonListLocal.set(index, obj); + } + if(indOpArray != -1) { + jsonListLocal = (List) obj; + } else { + jsonObjLocal = (Map) obj; + jsonListLocal = null; + } + } + } + + } + } + if(jsonListLocal != null) { + return accessListField(op, jsonListLocal, indexToAccess); + } else { + return accessObjField(op, jsonObjLocal, fieldName); + } + } + + private static Object accessObjField(OperationAccess op, Map jsonObjLocal, String fieldName) { + Object prevValue; + if (op.operation == DELETE_OPERATION) { + prevValue = jsonObjLocal.remove(fieldName); + } else if (op.operation == SET_OPERATION) { + prevValue = jsonObjLocal.put(fieldName, op.value); + } else { + prevValue = jsonObjLocal.get(fieldName); + } + return prevValue; + } + + private static Object accessListField(OperationAccess op, List jsonListLocal, int indexToAccess) { + Object prevValue; + int lastIndex = indexToAccess; + if (op.operation == DELETE_OPERATION) { + if (lastIndex >= jsonListLocal.size() || lastIndex < 0) { + prevValue = null; + } else { + prevValue = jsonListLocal.remove(lastIndex); + } + } else if (op.operation == SET_OPERATION) { + if (lastIndex == jsonListLocal.size() || lastIndex == -1) { + prevValue = null; + jsonListLocal.add(op.value); + } else if (lastIndex >= jsonListLocal.size() || lastIndex < 0) { + throw new IllegalArgumentException(String.format("Illegal access to %d position in array with size %d", + lastIndex, jsonListLocal.size())); + } else { + prevValue = jsonListLocal.set(lastIndex, op.value); + } + } else { + if (lastIndex >= jsonListLocal.size() || lastIndex < 0) { + prevValue = null; + } else { + prevValue = jsonListLocal.get(lastIndex); + } + } + return prevValue; + } + + @SuppressWarnings("unchecked") + public static List getIndexObjectByField(Object obj, List field, List res) { + if(obj == null) { + return res; + } + if(field.size() == 0) { + if(res == null) { + res = new ArrayList(); + } + res.add(obj); + return res; + } + if (obj instanceof Map) { + String fieldFirst = field.get(0); + Object value = ((Map) obj).get(fieldFirst); + return getIndexObjectByField(value, field.subList(1, field.size()), res); + } else if(obj instanceof Collection) { + for(Object o : ((Collection)obj)) { + res = getIndexObjectByField(o, field, res); + } + } else { + // we need extract but there no field + LOGGER.warn(String.format("Can't access field %s for object %s", field, obj)); + } + return res; + } + + +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/OUtils.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/OUtils.java new file mode 100644 index 0000000000..50ed65cba8 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/OUtils.java @@ -0,0 +1,122 @@ +package net.osmand.plus.osmedit.utils.util; + +import java.util.List; + +public class OUtils { + + public static boolean isEmpty(String s) { + return s == null || s.trim().length() == 0; + } + + public static boolean validateSqlIdentifier(String id, StringBuilder errorMessage, String field, String action) { + if(isEmpty(id)) { + errorMessage.append(String.format("Field '%s' is not specified which is necessary to %s", field, action)); + return false; + } + if(!isValidJavaIdentifier(id)) { + errorMessage.append(String.format("Value '%s' is not valid for %s to %s", id, field, action)); + return false; + } + return true; + } + + public static long combine(int x1, int x2) { + long l = Integer.toUnsignedLong(x1); + l = (l << 32) | Integer.toUnsignedLong(x2); + return l; + } + + public static int first(long l) { + long s = Integer.MAX_VALUE; + int t = (int) ((l & (s << 32)) >> 32); + if(l < 0) { + t = -t; + } + return t; + } + + public static int second(long l) { + int t = (int) (l & Integer.MAX_VALUE); + if ((l & 0x80000000l) != 0) { + t = -t; + } + return t; + } + + public static boolean isValidJavaIdentifier(String s) { + if (s.isEmpty()) { + return false; + } + if (!Character.isJavaIdentifierStart(s.charAt(0))) { + return false; + } + for (int i = 1; i < s.length(); i++) { + if (!Character.isJavaIdentifierPart(s.charAt(i))) { + return false; + } + } + return true; + } + + public static boolean equals(List s1, List s2) { + if(s1 == null || s1.size() == 0) { + return s2 == null || s2.size() == 0; + } + if(s2 == null || s1.size() != s2.size()) { + return false; + } + for(int i = 0; i < s1.size(); i++) { + Object o1 = s1.get(i); + Object o2 = s2.get(i); + if(o1 == null) { + if(o2 != null) { + return false; + } + } else { + if(!o1.equals(o2)) { + return false; + } + } + } + return true; + } + public static boolean equals(Object s1, Object s2) { + if(s1 == null || s2 == null) { + return s1 == s2; + } + if(s1 instanceof Number && s2 instanceof Number) { + return ((Number) s1).longValue() == ((Number) s2).longValue() && + ((Number) s1).doubleValue() == ((Number) s2).doubleValue(); + } + return s1.equals(s2); + } + + public static boolean equalsStringValue(Object s1, Object s2) { + if(s1 == null || s2 == null) { + return s1 == s2; + } + return s1.toString().equals(s2.toString()); + } + + public static long parseLongSilently(String input, long def) { + if (input != null && input.length() > 0) { + try { + return Long.parseLong(input); + } catch (NumberFormatException e) { + return def; + } + } + return def; + } + + public static int parseIntSilently(String input, int def) { + if (input != null && input.length() > 0) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + return def; + } + } + return def; + } +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/ConnectionException.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/ConnectionException.java new file mode 100644 index 0000000000..8a5d4504e3 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/ConnectionException.java @@ -0,0 +1,15 @@ +package net.osmand.plus.osmedit.utils.util.exception; + +public class ConnectionException extends TechnicalException { + + private static final long serialVersionUID = 8191498058841215578L; + + public ConnectionException(String message, Throwable cause) { + super(message, cause); + } + + public ConnectionException(String message) { + super(message); + } + +} diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/FailedVerificationException.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/FailedVerificationException.java new file mode 100644 index 0000000000..eba54a8260 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/FailedVerificationException.java @@ -0,0 +1,16 @@ +package net.osmand.plus.osmedit.utils.util.exception; + +public class FailedVerificationException extends Exception { + + private static final long serialVersionUID = -4936205097177668159L; + + + public FailedVerificationException(Exception e) { + super(e); + } + + + public FailedVerificationException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/TechnicalException.java b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/TechnicalException.java new file mode 100644 index 0000000000..70df91a4e8 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/osmedit/utils/util/exception/TechnicalException.java @@ -0,0 +1,15 @@ +package net.osmand.plus.osmedit.utils.util.exception; + +public class TechnicalException extends RuntimeException { + + private static final long serialVersionUID = 9201898433665734132L; + + public TechnicalException(String message, Throwable cause) { + super(message, cause); + } + + public TechnicalException(String message) { + super(message); + } + +}