Java tutorial
/* * GeoSolutions - GeoCollect * Copyright (C) 2014 - 2015 GeoSolutions (www.geo-solutions.it) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package it.geosolutions.geocollect.android.core.mission.utils; import it.geosolutions.android.map.utils.MapFilesProvider; import it.geosolutions.android.map.wfs.WFSGeoJsonFeatureLoader; import it.geosolutions.android.map.wfs.geojson.GeoJson; import it.geosolutions.android.map.wfs.geojson.feature.Feature; import it.geosolutions.geocollect.android.app.BuildConfig; import it.geosolutions.geocollect.android.app.R; import it.geosolutions.geocollect.android.core.login.LoginActivity; import it.geosolutions.geocollect.android.core.mission.Mission; import it.geosolutions.geocollect.android.core.mission.MissionFeature; import it.geosolutions.geocollect.android.core.mission.PendingMissionListActivity; import it.geosolutions.geocollect.model.config.MissionTemplate; import it.geosolutions.geocollect.model.source.XDataType; import it.geosolutions.geocollect.model.viewmodel.Field; import it.geosolutions.geocollect.model.viewmodel.Form; import it.geosolutions.geocollect.model.viewmodel.Page; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import jsqlite.Database; import jsqlite.Exception; import jsqlite.Stmt; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.preference.PreferenceManager; import android.support.v4.content.Loader; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import com.actionbarsherlock.app.SherlockFragmentActivity; import com.google.gson.Gson; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.PrecisionModel; import com.vividsolutions.jts.io.WKBReader; /** * @author Lorenzo Natali (lorenzo.natali@geo-solutions.it) * Utilities class for Mission */ public class MissionUtils { /** * TAG for Logging */ private static String TAG = "MissionUtils"; /** * The regex to parse the tags in the json */ private static final String TAG_REGEX = "\\$\\{(.*?)\\}"; private static final Pattern pattern = Pattern.compile(TAG_REGEX); /** * Patterns to replace in various templates */ public static String HEX_COLOR_PATTERN = "#XXXXXX"; public static String TABLE_NAME_PATTERN = "XXNAMEXX"; /** * Feature GCID string name */ public static final String GCID_STRING = "GCID"; /** * Create a loader getting the source of the mission * @param missionTemplate * @param page * @param pagesize * @return */ public static Loader<List<MissionFeature>> createMissionLoader(MissionTemplate missionTemplate, SherlockFragmentActivity activity, int page, int pagesize, Database db) { // Retrieve saved credentials for BasicAuth SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); String username = prefs.getString(LoginActivity.PREFS_USER_EMAIL, null); String password = prefs.getString(LoginActivity.PREFS_PASSWORD, null); WFSGeoJsonFeatureLoader wfsl = new WFSGeoJsonFeatureLoader(activity, missionTemplate.schema_seg.URL, missionTemplate.schema_seg.baseParams, missionTemplate.schema_seg.typeName, page * pagesize + 1, pagesize, username, password); return new SQLiteCascadeFeatureLoader(activity, wfsl, db, missionTemplate.schema_seg.localSourceStore, missionTemplate.schema_sop.localFormStore, missionTemplate.schema_seg.orderingField != null ? missionTemplate.schema_seg.orderingField : missionTemplate.orderingField, missionTemplate.priorityField, missionTemplate.priorityValuesColors); } /** * Provide the default template as configured in the raw folder. * @param c * @return */ public static MissionTemplate getDefaultTemplate(Context c) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); boolean usesDownloaded = prefs.getBoolean(PendingMissionListActivity.PREFS_USES_DOWNLOADED_TEMPLATE, false); if (usesDownloaded) { int index = prefs.getInt(PendingMissionListActivity.PREFS_DOWNLOADED_TEMPLATE_INDEX, 0); ArrayList<MissionTemplate> templates = PersistenceUtils.loadSavedTemplates(c); String selectedTemplateId = prefs.getString(PendingMissionListActivity.PREFS_SELECTED_TEMPLATE_ID, null); if (selectedTemplateId != null && !selectedTemplateId.isEmpty()) { for (MissionTemplate t : templates) { if (t.id != null && t.id.equalsIgnoreCase(selectedTemplateId)) { return t; } } } if (index >= templates.size()) { index = templates.size() - 1; } return templates.get(index); } else { InputStream inputStream = c.getResources().openRawResource(R.raw.defaulttemplate); if (inputStream != null) { final Gson gson = new Gson(); final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); // TODO: Catch JsonSyntaxException when template is malformed return gson.fromJson(reader, MissionTemplate.class); } } return null; } /** * converts the Json string to an inputstream and parses it using gson * @param json String * @return the parsed template */ public static MissionTemplate getTemplateFromJSON(final String json) { final Gson gson = new Gson(); final BufferedReader reader = new BufferedReader( new InputStreamReader(new ByteArrayInputStream(json.getBytes()))); return gson.fromJson(reader, MissionTemplate.class); } /** * Parse the string to get the tags between {} (brackets) * @param toParse the string to parse * @return the list of brackets */ public static List<String> getTags(String toParse) { if (toParse == null) { return null; } Matcher matcher = pattern.matcher(toParse); //gets the while (matcher.find()) { List<String> tags = new ArrayList<String>(); int pos = -1; while (matcher.find(pos + 1)) { pos = matcher.start(); tags.add(matcher.group(1)); } return tags; } return null; } /** * @param dataMapping * @return */ public static String generateJsonString(Map<String, String> dataMapping, Mission m) { Feature f = PersistenceUtils.loadFeatureById(m); GeoJson gson = new GeoJson(); String c = gson.toJson(f); try { return new String(c.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { // return the original string return c; } } /** * checks if mandatory fields were compiled using the mandatory field of the defaulttemplate * * @param form the form to check * @param id to refer to * @param db to read from * @param tableName to refer to * @return ArrayList with the fields label which was not compiled or an empty list if all was done */ public static ArrayList<String> checkIfAllMandatoryFieldsAreSatisfied(final Form form, final String id, final Database db, final String tableName) { ArrayList<String> missingEntries = new ArrayList<String>(); Stmt st = null; //find mandatory fields ArrayList<Pair<String, String>> missingFieldIDs = new ArrayList<Pair<String, String>>(); for (Page page : form.pages) { for (Field f : page.fields) { if (f.mandatory) { missingFieldIDs.add(new Pair<String, String>(f.fieldId, f.label)); } } } //if no mandatory fields no need to continue if (missingFieldIDs.isEmpty()) { return missingEntries; } //create selection String selection = ""; for (int i = 0; i < missingFieldIDs.size(); i++) { selection += missingFieldIDs.get(i).first; if (i < missingFieldIDs.size() - 1) { selection += ","; } } //create query final String s = "SELECT " + selection + " FROM '" + tableName + "' WHERE ORIGIN_ID = '" + id + "';"; //do the query if (jsqlite.Database.complete(s)) { try { st = db.prepare(s); if (st.step()) { for (int j = 0; j < st.column_count(); j++) { //if mandatory field is null or empty, add it to the missing entries if (st.column_string(j) == null || st.column_string(j).equals("")) { missingEntries.add(missingFieldIDs.get(j).second); } } } } catch (Exception e) { Log.d(MissionUtils.class.getSimpleName(), "Error checkIfAllMandatoryFieldsArsSatisfied", e); } } else { if (BuildConfig.DEBUG) { Log.w(TAG, "Query is not complete: " + s); } } return missingEntries; } /** * get "created" {@link MissionFeature} from the database * @param tableName * @param db * @return a list of created {@link MissionFeature} */ public static ArrayList<MissionFeature> getMissionFeatures(final String tableName, final Database db) { return getMissionFeatures(tableName, db, null); } /** * get "created" {@link MissionFeature} from the database, adding the distance property if possible * @param tableName * @param db * @return a list of created {@link MissionFeature} */ public static ArrayList<MissionFeature> getMissionFeatures(final String mTableName, final Database db, Context ctx) { ArrayList<MissionFeature> mFeaturesList = new ArrayList<MissionFeature>(); String tableName = mTableName; // Reader for the Geometry field WKBReader wkbReader = new WKBReader(); //create query //////////////////////////////////////////////////////////////// // SQLite Geometry cannot be read with direct wkbreader // We must do a double conversion with ST_AsBinary and CastToXY //////////////////////////////////////////////////////////////// // Cycle all the columns to find the "Point" type one HashMap<String, String> columns = SpatialiteUtils.getPropertiesFields(db, tableName); if (columns == null) { if (BuildConfig.DEBUG) { Log.w(TAG, "Cannot retrieve columns from database"); } return mFeaturesList; } List<String> selectFields = new ArrayList<String>(); for (String columnName : columns.keySet()) { //Spatialite custom field point if ("point".equalsIgnoreCase(columns.get(columnName))) { selectFields.add("ST_AsBinary(CastToXY(" + columnName + ")) AS GEOMETRY"); } else { selectFields.add(columnName); } } // Merge all the column names String selectString = TextUtils.join(",", selectFields); if (ctx != null) { SharedPreferences prefs = ctx.getSharedPreferences(SQLiteCascadeFeatureLoader.PREF_NAME, Context.MODE_PRIVATE); boolean useDistance = prefs.getBoolean(SQLiteCascadeFeatureLoader.ORDER_BY_DISTANCE, false); double posX = Double.longBitsToDouble( prefs.getLong(SQLiteCascadeFeatureLoader.LOCATION_X, Double.doubleToLongBits(0))); double posY = Double.longBitsToDouble( prefs.getLong(SQLiteCascadeFeatureLoader.LOCATION_Y, Double.doubleToLongBits(0))); if (useDistance) { selectString = selectString + ", Distance(ST_Transform(GEOMETRY,4326), MakePoint(" + posX + "," + posY + ", 4326)) * 111195 AS '" + MissionFeature.DISTANCE_VALUE_ALIAS + "'"; } //Add Spatial filtering int filterSrid = prefs.getInt(SQLiteCascadeFeatureLoader.FILTER_SRID, -1); // If the SRID is not defined, skip the filter if (filterSrid != -1) { double filterN = Double.longBitsToDouble( prefs.getLong(SQLiteCascadeFeatureLoader.FILTER_N, Double.doubleToLongBits(0))); double filterS = Double.longBitsToDouble( prefs.getLong(SQLiteCascadeFeatureLoader.FILTER_S, Double.doubleToLongBits(0))); double filterW = Double.longBitsToDouble( prefs.getLong(SQLiteCascadeFeatureLoader.FILTER_W, Double.doubleToLongBits(0))); double filterE = Double.longBitsToDouble( prefs.getLong(SQLiteCascadeFeatureLoader.FILTER_E, Double.doubleToLongBits(0))); tableName += " WHERE MbrIntersects(GEOMETRY, BuildMbr(" + filterW + ", " + filterN + ", " + filterE + ", " + filterS + ")) "; } } // Build the query StringWriter queryWriter = new StringWriter(); queryWriter.append("SELECT ").append(selectString).append(" FROM ").append(tableName).append(";"); // The resulting query String query = queryWriter.toString(); Stmt stmt; //do the query if (jsqlite.Database.complete(query)) { try { if (BuildConfig.DEBUG) { Log.i("getCreatedMissionFeatures", "Loading from query: " + query); } stmt = db.prepare(query); MissionFeature f; while (stmt.step()) { f = new MissionFeature(); SpatialiteUtils.populateFeatureFromStmt(wkbReader, stmt, f); if (f.geometry == null) { //workaround for a bug which does not read out the "Point" geometry in WKBreader //read single x and y coordinates instead and create the geometry by hand double[] xy = PersistenceUtils.getXYCoord(db, tableName, f.id); if (xy != null) { GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 4326); f.geometry = geometryFactory.createPoint(new Coordinate(xy[0], xy[1])); } } f.typeName = mTableName; mFeaturesList.add(f); } stmt.close(); } catch (Exception e) { Log.d(TAG, "Error getCreatedMissions", e); } } else { if (BuildConfig.DEBUG) { Log.w(TAG, "Query is not complete: " + query); } } return mFeaturesList; } /** * Modifies the Feature aligning the field types with the give ones * @param inputFeature * @param fieldTypes */ public static void alignPropertiesTypes(Feature inputFeature, HashMap<String, XDataType> fieldTypes) { if (inputFeature == null || fieldTypes == null || inputFeature.properties == null || inputFeature.properties.isEmpty()) { // Nothing to do return; } ArrayList<String> propList = new ArrayList<String>(); for (String s : inputFeature.properties.keySet()) { propList.add(s); } // Cycle the properties for (String propertyString : propList) { if (fieldTypes.containsKey(propertyString)) { if (inputFeature.properties.get(propertyString) instanceof String) { // Get property value String pValue = (String) inputFeature.properties.get(propertyString); if (fieldTypes.get(propertyString) == XDataType.integer) { if (pValue == null || pValue.isEmpty()) { // inputFeature.properties.remove(propertyString); } try { inputFeature.properties.put(propertyString, Integer.parseInt(pValue)); } catch (NumberFormatException nfe) { Log.w(TAG, "Wrong Integer String format, removing property " + propertyString + "with value " + pValue); inputFeature.properties.remove(propertyString); } } else if (fieldTypes.get(propertyString) == XDataType.decimal || fieldTypes.get(propertyString) == XDataType.real) { if (pValue == null || pValue.isEmpty()) { // inputFeature.properties.remove(propertyString); } try { inputFeature.properties.put(propertyString, Double.parseDouble(pValue)); } catch (NumberFormatException nfe) { Log.w(TAG, "Wrong Double String format, removing property " + propertyString + "with value " + pValue); inputFeature.properties.remove(propertyString); } } } } } } /** * Returns the "GCID" field of the origin feature of the given Mission * @param mMission * @return */ public static String getMissionGCID(Mission mission) { if (mission == null || mission.getOrigin() == null) { if (BuildConfig.DEBUG) { Log.w(TAG, "WARNING: cannot find origin Feature"); } return null; } return getFeatureGCID(mission.getOrigin()); } /** * Returns the "GCID" or "gcid" field of the given Feature * Returns null otherwise * @param feature * @return */ public static String getFeatureGCID(Feature feature) { if (feature == null) { if (BuildConfig.DEBUG) { Log.w(TAG, "WARNING: cannot find feature GCID (feature null)"); } return null; } // Default ID String originIDString = feature.id; if (feature.properties != null) { String localGCID = null; if (feature.properties.containsKey(GCID_STRING)) { localGCID = GCID_STRING; } else if (feature.properties.containsKey(GCID_STRING.toLowerCase(Locale.US))) { localGCID = GCID_STRING.toLowerCase(Locale.US); } if (localGCID != null) { try { Object objID = feature.properties.get(localGCID); if (objID == null) { if (BuildConfig.DEBUG) { Log.w(TAG, "WARNING: Feature has a null GCID using feature id: " + originIDString); } return originIDString; } originIDString = (String) objID; } catch (ClassCastException cce) { if (BuildConfig.DEBUG) { Log.w(TAG, "WARNING: Feature has a GCID but it cannot be converted to String"); } originIDString = null; } } } return originIDString; } /** * Check the existence of map styles relative to the given {@link MissionTemplate} * If they don't exist, it create them * @param missionTemplate */ public static void checkMapStyles(Resources resources, MissionTemplate missionTemplate) { if (resources == null || missionTemplate == null) { return; } File styleDir = new File(MapFilesProvider.getStyleDirIn()); if (!styleDir.exists()) { // Create the directory if not exists styleDir.mkdirs(); } else if (!styleDir.isDirectory()) { if (BuildConfig.DEBUG) { Log.w(TAG, "Style directory is not a directory!"); } return; } String baseString; try { baseString = PersistenceUtils.loadBaseStyleFile(resources); } catch (IOException e1) { Log.e(TAG, e1.getLocalizedMessage(), e1); return; } if (baseString == null) { return; } if (missionTemplate.schema_seg != null && missionTemplate.schema_seg.localSourceStore != null) { // Check for the main style File mainStyleFile = new File(styleDir, missionTemplate.schema_seg.localSourceStore + ".style"); if (!mainStyleFile.exists()) { String noticeStyleString = baseString.replace(HEX_COLOR_PATTERN, resources.getString(R.color.default_notice_color)); noticeStyleString = noticeStyleString.replace(TABLE_NAME_PATTERN, missionTemplate.schema_seg.localSourceStore); writeStyleFile(noticeStyleString, mainStyleFile); } // Check for the new notices style File newStyleFile = new File(styleDir, missionTemplate.schema_seg.localSourceStore + MissionTemplate.NEW_NOTICE_SUFFIX + ".style"); if (!newStyleFile.exists()) { String newStyleString = baseString.replace(HEX_COLOR_PATTERN, resources.getString(R.color.default_new_notice_color)); newStyleString = newStyleString.replace(TABLE_NAME_PATTERN, missionTemplate.schema_seg.localSourceStore + MissionTemplate.NEW_NOTICE_SUFFIX); writeStyleFile(newStyleString, newStyleFile); } } if (missionTemplate.schema_sop != null && missionTemplate.schema_sop.localFormStore != null) { // Check for the Surveys style File surveyStyleFile = new File(styleDir, missionTemplate.schema_sop.localFormStore + ".style"); if (!surveyStyleFile.exists()) { String surveyStyleString = baseString.replace(HEX_COLOR_PATTERN, resources.getString(R.color.default_survey_color)); surveyStyleString = surveyStyleString.replace(TABLE_NAME_PATTERN, missionTemplate.schema_sop.localFormStore); writeStyleFile(surveyStyleString, surveyStyleFile); } } } /** * Write the String into the given File * @param baseString * @param mainStyleFile */ public static void writeStyleFile(String baseString, File mainStyleFile) { FileWriter fw = null; try { fw = new FileWriter(mainStyleFile); fw.write(baseString); Log.i("STYLE", "Style File updated:" + mainStyleFile.getPath()); } catch (FileNotFoundException e) { Log.e("STYLE", "unable to write open file:" + mainStyleFile.getPath()); } catch (IOException e) { Log.e("STYLE", "error writing the file: " + mainStyleFile.getPath()); } finally { try { if (fw != null) { fw.close(); } } catch (IOException e) { // ignored } } } /** * checks if all files which are defined in the config section of * the template are present in the applications sdcard folder * @param t the template to check * @return true if all files are present - false otherwise */ @SuppressWarnings("rawtypes") public static boolean checkTemplateForBackgroundData(final Context context, final MissionTemplate t) { final String mount = MapFilesProvider.getEnvironmentDirPath(context); final String baseDir = MapFilesProvider.getBaseDir(); final String appDir = mount + baseDir; final HashMap<String, Object> config = t.config; if (config.containsKey("bgFiles")) { ArrayList<Map> urls = (ArrayList<Map>) config.get("bgFiles"); if (urls != null) { for (int i = 0; i < urls.size(); i++) { Map ltm = urls.get(i); //String url = (String) ltm.get("url"); ArrayList<Map> content = (ArrayList<Map>) ltm.get("content"); for (Map item : content) { final String fileName = (String) item.get("file"); final File file = new File(appDir + "/" + fileName); if (!file.exists()) { return false; } } } } } return true; } /** * creates and returns a HashMap containing entries consisting of url to zip files and the amount of files * these zips contain * @param t the template to parse * @return HashMap containing map of urls to fileAmounts */ @SuppressWarnings("rawtypes") public static HashMap<String, Integer> getContentUrlsAndFileAmountForTemplate(final MissionTemplate t) { final HashMap<String, Integer> urls = new HashMap<String, Integer>(); final HashMap<String, Object> config = t.config; if (config.containsKey("bgFiles")) { ArrayList<Map> files = (ArrayList<Map>) config.get("bgFiles"); if (files != null) { for (int i = 0; i < files.size(); i++) { Map ltm = files.get(i); String url = (String) ltm.get("url"); ArrayList<Map> content = (ArrayList<Map>) ltm.get("content"); urls.put(url, content.size()); } } } return urls; } /** * Generates a new {@link MissionFeature} with the correct properties casing suitable for the upload. * Removes unnecessary properties * Takes feature schema as input */ public static MissionFeature alignMissionFeatureProperties(MissionFeature inputMissionFeature, HashMap<String, XDataType> schema) { if (inputMissionFeature == null || schema == null) { if (BuildConfig.DEBUG) { Log.w(TAG, "NULL MissionFeature or schema, cannot convert."); } return null; } MissionFeature output = new MissionFeature(); output.typeName = inputMissionFeature.typeName; output.id = inputMissionFeature.id; output.displayColor = inputMissionFeature.displayColor; output.editing = inputMissionFeature.editing; if (inputMissionFeature.geometry != null) { output.geometry = (Geometry) inputMissionFeature.geometry.clone(); } output.geometry_name = inputMissionFeature.geometry_name; output.type = inputMissionFeature.type; output.properties = new HashMap<String, Object>(); if (inputMissionFeature.properties != null) { for (String inputKey : inputMissionFeature.properties.keySet()) { for (String schemaKey : schema.keySet()) { if (schemaKey != null && schemaKey.equalsIgnoreCase(inputKey)) { output.properties.put(schemaKey, inputMissionFeature.properties.get(inputKey)); } } } } return output; } /** * MissionTemplate Comparator * This is based on Template ID, all other fields are ignored * @author Lorenzo Pini (lorenzo.pini@geo-solutions.it) */ public static class MissionTemplateComparator implements Comparator<MissionTemplate> { @Override public int compare(MissionTemplate o1, MissionTemplate o2) { if (o1 == o2) { return 0; } if (o1 == null) { return -1; } if (o2 == null) { return 1; } if (o1.id == null || o1.id.isEmpty()) { return -1; } if (o2.id == null || o2.id.isEmpty()) { return -1; } return o1.id.compareTo(o2.id); } } }