diff options
-rw-r--r-- | main/res/values-de/strings.xml | 1 | ||||
-rw-r--r-- | main/res/values/strings.xml | 1 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/StaticMapsProvider.java | 37 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/cgBase.java | 1 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java | 57 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java | 22 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/concurrent/Task.java | 16 | ||||
-rw-r--r-- | main/src/cgeo/geocaching/files/GPXImporter.java | 37 | ||||
-rw-r--r-- | tests/src/cgeo/geocaching/files/GPXImporterTest.java | 34 |
9 files changed, 180 insertions, 26 deletions
diff --git a/main/res/values-de/strings.xml b/main/res/values-de/strings.xml index 5929d5f..b88d066 100644 --- a/main/res/values-de/strings.xml +++ b/main/res/values-de/strings.xml @@ -582,6 +582,7 @@ <!-- gpx --> <string name="gpx_import_loading_caches">Lade Caches aus GPX-Datei</string> <string name="gpx_import_loading_waypoints">Lade Wegpunkte aus GPX-Datei</string> + <string name="gpx_import_store_static_maps">Schreibe statische Karten</string> <string name="gpx_import_storing">Schreibe Caches in Datenbank</string> <string name="gpx_import_caches_imported">Caches importiert.</string> <string name="gpx_import_title">Importiere GPX-Datei</string> diff --git a/main/res/values/strings.xml b/main/res/values/strings.xml index 7264019..2f32f4f 100644 --- a/main/res/values/strings.xml +++ b/main/res/values/strings.xml @@ -595,6 +595,7 @@ <!-- gpx --> <string name="gpx_import_loading_caches">Loading caches from .gpx file</string> <string name="gpx_import_loading_waypoints">Loading waypoints file</string> + <string name="gpx_import_store_static_maps">Storing static maps</string> <string name="gpx_import_storing">Writing caches to database</string> <string name="gpx_import_caches_imported">caches imported</string> <string name="gpx_import_title">Import GPX</string> diff --git a/main/src/cgeo/geocaching/StaticMapsProvider.java b/main/src/cgeo/geocaching/StaticMapsProvider.java index cd23489..1461e59 100644 --- a/main/src/cgeo/geocaching/StaticMapsProvider.java +++ b/main/src/cgeo/geocaching/StaticMapsProvider.java @@ -1,5 +1,7 @@ package cgeo.geocaching; +import cgeo.geocaching.concurrent.BlockingThreadPool; +import cgeo.geocaching.concurrent.Task; import cgeo.geocaching.files.LocalStorage; import cgeo.geocaching.geopoint.GeopointFormatter.Format; @@ -14,13 +16,14 @@ import android.view.Display; import android.view.WindowManager; import java.io.File; +import java.util.concurrent.TimeUnit; public class StaticMapsProvider { private static final String MARKERS_URL = "http://cgeo.carnero.cc/_markers/"; - /** - * in my tests the "no image available" image had 5470 bytes, while "street only" maps had at least 20000 bytes - */ + /** In my tests the "no image available" image had 5470 bytes, while "street only" maps had at least 20000 bytes */ private static final int MIN_MAP_IMAGE_BYTES = 6000; + /** ThreadPool restricting this to 1 Thread. **/ + private static final BlockingThreadPool pool = new BlockingThreadPool(1, Thread.MIN_PRIORITY); public static File getMapFile(final String geocode, String prefix, final int level, final boolean createDirs) { return LocalStorage.getStorageFile(geocode, "map_" + prefix + level, false, createDirs); @@ -51,12 +54,22 @@ public class StaticMapsProvider { } } + public static void downloadMaps(cgCache cache, cgeoapplication app) { + final Display display = ((WindowManager) app.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + downloadMaps(cache, display); + } + public static void downloadMaps(cgCache cache, Activity activity) { + final Display display = ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + downloadMaps(cache, display); + } + + public static void downloadMaps(cgCache cache, Display display) { if ((!Settings.isStoreOfflineMaps() && !Settings.isStoreOfflineWpMaps()) || StringUtils.isBlank(cache.getGeocode())) { return; } - int edge = guessMinDisplaySide(activity); + int edge = guessMinDisplaySide(display); if (Settings.isStoreOfflineMaps() && cache.getCoords() != null) { storeCacheStaticMap(cache, edge, false); @@ -110,8 +123,7 @@ public class StaticMapsProvider { downloadMaps(cache, cacheMarkerUrl, "", latlonMap, edge, waypoints.toString(), waitForResult); } - private static int guessMinDisplaySide(Activity activity) { - final Display display = ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + private static int guessMinDisplaySide(Display display) { final int maxWidth = display.getWidth() - 25; final int maxHeight = display.getHeight() - 25; int edge = 0; @@ -123,20 +135,27 @@ public class StaticMapsProvider { return edge; } + private static int guessMinDisplaySide(Activity activity) { + return guessMinDisplaySide(((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()); + } + private static void downloadMaps(final cgCache cache, final String markerUrl, final String prefix, final String latlonMap, final int edge, final String waypoints, boolean waitForResult) { if (waitForResult) { downloadDifferentZooms(cache, markerUrl, prefix, latlonMap, edge, waypoints); } else { - Thread staticMapsThread = new Thread("getting static map") { + Task currentTask = new Task("getting static map") { @Override public void run() { downloadDifferentZooms(cache, markerUrl, prefix, latlonMap, edge, waypoints); } }; - staticMapsThread.setPriority(Thread.MIN_PRIORITY); - staticMapsThread.start(); + try { + pool.add(currentTask, 20, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Log.e(Settings.tag, "StaticMapsProvider.downloadMaps error adding task: " + e.toString()); + } } } diff --git a/main/src/cgeo/geocaching/cgBase.java b/main/src/cgeo/geocaching/cgBase.java index 8db3494..0e2f642 100644 --- a/main/src/cgeo/geocaching/cgBase.java +++ b/main/src/cgeo/geocaching/cgBase.java @@ -28,7 +28,6 @@ import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.CancellableHandler; import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; diff --git a/main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java b/main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java new file mode 100644 index 0000000..8e60d44 --- /dev/null +++ b/main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java @@ -0,0 +1,57 @@ +package cgeo.geocaching.concurrent; + + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * BlockingThreadPool restricts the amount of parallel threads executing Runnables. + */ +public class BlockingThreadPool { + /** The queue holding the Runnable. **/ + private BlockingQueue<Runnable> queue = null; + /** The Executor. **/ + private ThreadPoolExecutor executor; + + /** + * Creates a ThreadPool with a given maximum of parallel threads running. + * Idle threads will be stopped until new threads are added. + * + * @param poolSize + * Maximum amout of parallel Threads + * @param priority + * The Thread priority e.g. Thread.MIN_PRIORITY + */ + public BlockingThreadPool(int poolSize, int priority) { + ThreadFactory threadFactory = new PriorityThreadFactory(priority); + this.queue = new ArrayBlockingQueue<Runnable>(poolSize, true); + this.executor = new ThreadPoolExecutor(0, poolSize, 5, TimeUnit.SECONDS, this.queue); + this.executor.setThreadFactory(threadFactory); + } + + /** + * Add a runnable to the pool. This will start the core threads in the underlying + * executor and try to add the Runnable to the pool. This method waits until timeout + * if no free thread is available. + * + * @param task + * The Runnable to add to the pool + * @param timeout + * The timeout to wait for a free thread + * @param unit + * The timeout unit + * @return true/false successful added + * @throws InterruptedException + * Operation was interrupted + */ + public boolean add(Runnable task, int timeout, TimeUnit unit) throws InterruptedException { + this.executor.setCorePoolSize(this.executor.getMaximumPoolSize()); + this.executor.prestartAllCoreThreads(); + boolean successfull = this.queue.offer(task, timeout, unit); + this.executor.setCorePoolSize(0); + return successfull; + } +} diff --git a/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java b/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java new file mode 100644 index 0000000..76379de --- /dev/null +++ b/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java @@ -0,0 +1,22 @@ +package cgeo.geocaching.concurrent; + +import java.util.concurrent.ThreadFactory; + +/** + * Helper class for setting Thread priority in ThreadPool. + */ +public class PriorityThreadFactory implements ThreadFactory { + private int priority; + + public PriorityThreadFactory(int priority) { + this.priority = priority; + } + + @Override + public Thread newThread(Runnable r) { + Thread result = new Thread(r); + result.setPriority(this.priority); + return result; + } + +} diff --git a/main/src/cgeo/geocaching/concurrent/Task.java b/main/src/cgeo/geocaching/concurrent/Task.java new file mode 100644 index 0000000..2472538 --- /dev/null +++ b/main/src/cgeo/geocaching/concurrent/Task.java @@ -0,0 +1,16 @@ +package cgeo.geocaching.concurrent; + +/** + * Basic class for Runnables added to ThreadPool. + */ +public abstract class Task implements Runnable { + private String name = null; + + public Task(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } +} diff --git a/main/src/cgeo/geocaching/files/GPXImporter.java b/main/src/cgeo/geocaching/files/GPXImporter.java index 3d1249f..dde5511 100644 --- a/main/src/cgeo/geocaching/files/GPXImporter.java +++ b/main/src/cgeo/geocaching/files/GPXImporter.java @@ -3,10 +3,12 @@ package cgeo.geocaching.files; import cgeo.geocaching.R;
import cgeo.geocaching.SearchResult;
import cgeo.geocaching.Settings;
+import cgeo.geocaching.StaticMapsProvider;
import cgeo.geocaching.cgCache;
import cgeo.geocaching.cgeoapplication;
import cgeo.geocaching.activity.IAbstractActivity;
import cgeo.geocaching.activity.Progress;
+import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.enumerations.LoadFlags.SaveFlag;
import cgeo.geocaching.utils.CancellableHandler;
@@ -32,6 +34,7 @@ import java.util.Collection; import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@@ -41,10 +44,11 @@ public class GPXImporter { static final int IMPORT_STEP_READ_FILE = 1;
static final int IMPORT_STEP_READ_WPT_FILE = 2;
static final int IMPORT_STEP_STORE_CACHES = 3;
- static final int IMPORT_STEP_FINISHED = 4;
- static final int IMPORT_STEP_FINISHED_WITH_ERROR = 5;
- static final int IMPORT_STEP_CANCEL = 6;
- static final int IMPORT_STEP_CANCELED = 7;
+ static final int IMPORT_STEP_STORE_STATIC_MAPS = 4;
+ static final int IMPORT_STEP_FINISHED = 5;
+ static final int IMPORT_STEP_FINISHED_WITH_ERROR = 6;
+ static final int IMPORT_STEP_CANCEL = 7;
+ static final int IMPORT_STEP_CANCELED = 8;
public static final String GPX_FILE_EXTENSION = ".gpx";
public static final String ZIP_FILE_EXTENSION = ".zip";
@@ -140,6 +144,11 @@ public class GPXImporter { SearchResult search = storeParsedCaches(caches);
Log.i(Settings.tag, "Imported successfully " + caches.size() + " caches.");
+ if (Settings.isStoreOfflineMaps() || Settings.isStoreOfflineWpMaps()) {
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_STORE_STATIC_MAPS, R.string.gpx_import_store_static_maps, search.getCount()));
+ importStaticMaps(search);
+ }
+
importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED, search.getCount(), 0, search));
} catch (IOException e) {
Log.i(Settings.tag, "Importing caches failed - error reading data: " + e.getMessage());
@@ -175,6 +184,21 @@ public class GPXImporter { }
return search;
}
+
+ private void importStaticMaps(final SearchResult importedCaches) {
+ final cgeoapplication app = cgeoapplication.getInstance();
+ Set<cgCache> caches = importedCaches.getCachesFromSearchResult(LoadFlags.LOAD_WAYPOINTS);
+ int storedCacheMaps = 0;
+ for (cgCache cache : caches) {
+ Log.d(Settings.tag, "GPXImporter.ImportThread.importStaticMaps start downloadMaps");
+ StaticMapsProvider.downloadMaps(cache, app);
+ storedCacheMaps++;
+ if (progressHandler.isCancelled()) {
+ throw new CancellationException();
+ }
+ progressHandler.sendMessage(progressHandler.obtainMessage(0, storedCacheMaps, 0));
+ }
+ }
}
static class ImportLocFileThread extends ImportThread {
@@ -367,6 +391,11 @@ public class GPXImporter { progress.setMaxProgressAndReset(msg.arg2);
break;
+ case IMPORT_STEP_STORE_STATIC_MAPS:
+ progress.setMessage(res.getString(msg.arg1));
+ progress.setMaxProgressAndReset(msg.arg2);
+ break;
+
case IMPORT_STEP_FINISHED:
progress.dismiss();
fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), msg.arg1 + " " + res.getString(R.string.gpx_import_caches_imported));
diff --git a/tests/src/cgeo/geocaching/files/GPXImporterTest.java b/tests/src/cgeo/geocaching/files/GPXImporterTest.java index 33a1524..efc892b 100644 --- a/tests/src/cgeo/geocaching/files/GPXImporterTest.java +++ b/tests/src/cgeo/geocaching/files/GPXImporterTest.java @@ -25,6 +25,8 @@ public class GPXImporterTest extends AbstractResourceInstrumentationTestCase { private TestHandler progressHandler = new TestHandler(); private int listId; private File tempDir; + private boolean importCacheStaticMaps; + private boolean importWpStaticMaps; public void testGetWaypointsFileNameForGpxFile() throws IOException { String[] gpxFiles = new String[] { "1234567.gpx", "1.gpx", "1234567.9.gpx", @@ -58,13 +60,14 @@ public class GPXImporterTest extends AbstractResourceInstrumentationTestCase { GPXImporter.ImportGpxFileThread importThread = new GPXImporter.ImportGpxFileThread(gc31j2h, listId, importStepHandler, progressHandler); runImportThread(importThread); - assertEquals(4, importStepHandler.messages.size()); + assertEquals(5, importStepHandler.messages.size()); Iterator<Message> iMsg = importStepHandler.messages.iterator(); assertEquals(GPXImporter.IMPORT_STEP_START, iMsg.next().what); assertEquals(GPXImporter.IMPORT_STEP_READ_FILE, iMsg.next().what); assertEquals(GPXImporter.IMPORT_STEP_STORE_CACHES, iMsg.next().what); + assertEquals(GPXImporter.IMPORT_STEP_STORE_STATIC_MAPS, iMsg.next().what); assertEquals(GPXImporter.IMPORT_STEP_FINISHED, iMsg.next().what); - SearchResult search = (SearchResult) importStepHandler.messages.get(3).obj; + SearchResult search = (SearchResult) importStepHandler.messages.get(4).obj; assertEquals(Collections.singletonList("GC31J2H"), new ArrayList<String>(search.getGeocodes())); cgCache cache = cgeoapplication.getInstance().loadCache("GC31J2H", LoadFlags.LOAD_CACHE_OR_DB); @@ -92,8 +95,8 @@ public class GPXImporterTest extends AbstractResourceInstrumentationTestCase { GPXImporter.ImportGpxFileThread importThread = new GPXImporter.ImportGpxFileThread(gc31j2h, listId, importStepHandler, progressHandler); runImportThread(importThread); - assertImportStepMessages(GPXImporter.IMPORT_STEP_START, GPXImporter.IMPORT_STEP_READ_FILE, GPXImporter.IMPORT_STEP_READ_WPT_FILE, GPXImporter.IMPORT_STEP_STORE_CACHES, GPXImporter.IMPORT_STEP_FINISHED); - SearchResult search = (SearchResult) importStepHandler.messages.get(4).obj; + assertImportStepMessages(GPXImporter.IMPORT_STEP_START, GPXImporter.IMPORT_STEP_READ_FILE, GPXImporter.IMPORT_STEP_READ_WPT_FILE, GPXImporter.IMPORT_STEP_STORE_CACHES, GPXImporter.IMPORT_STEP_STORE_STATIC_MAPS, GPXImporter.IMPORT_STEP_FINISHED); + SearchResult search = (SearchResult) importStepHandler.messages.get(5).obj; assertEquals(Collections.singletonList("GC31J2H"), new ArrayList<String>(search.getGeocodes())); cgCache cache = cgeoapplication.getInstance().loadCache("GC31J2H", LoadFlags.LOAD_CACHE_OR_DB); @@ -115,8 +118,8 @@ public class GPXImporterTest extends AbstractResourceInstrumentationTestCase { GPXImporter.ImportLocFileThread importThread = new GPXImporter.ImportLocFileThread(oc5952, listId, importStepHandler, progressHandler); runImportThread(importThread); - assertImportStepMessages(GPXImporter.IMPORT_STEP_START, GPXImporter.IMPORT_STEP_READ_FILE, GPXImporter.IMPORT_STEP_STORE_CACHES, GPXImporter.IMPORT_STEP_FINISHED); - SearchResult search = (SearchResult) importStepHandler.messages.get(3).obj; + assertImportStepMessages(GPXImporter.IMPORT_STEP_START, GPXImporter.IMPORT_STEP_READ_FILE, GPXImporter.IMPORT_STEP_STORE_CACHES, GPXImporter.IMPORT_STEP_STORE_STATIC_MAPS, GPXImporter.IMPORT_STEP_FINISHED); + SearchResult search = (SearchResult) importStepHandler.messages.get(4).obj; assertEquals(Collections.singletonList("OC5952"), new ArrayList<String>(search.getGeocodes())); cgCache cache = cgeoapplication.getInstance().loadCache("OC5952", LoadFlags.LOAD_CACHE_OR_DB); @@ -156,8 +159,8 @@ public class GPXImporterTest extends AbstractResourceInstrumentationTestCase { GPXImporter.ImportGpxAttachmentThread importThread = new GPXImporter.ImportGpxAttachmentThread(uri, getInstrumentation().getContext().getContentResolver(), listId, importStepHandler, progressHandler); runImportThread(importThread); - assertImportStepMessages(GPXImporter.IMPORT_STEP_START, GPXImporter.IMPORT_STEP_READ_FILE, GPXImporter.IMPORT_STEP_STORE_CACHES, GPXImporter.IMPORT_STEP_FINISHED); - SearchResult search = (SearchResult) importStepHandler.messages.get(3).obj; + assertImportStepMessages(GPXImporter.IMPORT_STEP_START, GPXImporter.IMPORT_STEP_READ_FILE, GPXImporter.IMPORT_STEP_STORE_CACHES, GPXImporter.IMPORT_STEP_STORE_STATIC_MAPS, GPXImporter.IMPORT_STEP_FINISHED); + SearchResult search = (SearchResult) importStepHandler.messages.get(4).obj; assertEquals(Collections.singletonList("GC31J2H"), new ArrayList<String>(search.getGeocodes())); cgCache cache = cgeoapplication.getInstance().loadCache("GC31J2H", LoadFlags.LOAD_CACHE_OR_DB); @@ -174,8 +177,8 @@ public class GPXImporterTest extends AbstractResourceInstrumentationTestCase { GPXImporter.ImportGpxZipFileThread importThread = new GPXImporter.ImportGpxZipFileThread(pq7545915, listId, importStepHandler, progressHandler); runImportThread(importThread); - assertImportStepMessages(GPXImporter.IMPORT_STEP_START, GPXImporter.IMPORT_STEP_READ_FILE, GPXImporter.IMPORT_STEP_READ_WPT_FILE, GPXImporter.IMPORT_STEP_STORE_CACHES, GPXImporter.IMPORT_STEP_FINISHED); - SearchResult search = (SearchResult) importStepHandler.messages.get(4).obj; + assertImportStepMessages(GPXImporter.IMPORT_STEP_START, GPXImporter.IMPORT_STEP_READ_FILE, GPXImporter.IMPORT_STEP_READ_WPT_FILE, GPXImporter.IMPORT_STEP_STORE_CACHES, GPXImporter.IMPORT_STEP_STORE_STATIC_MAPS, GPXImporter.IMPORT_STEP_FINISHED); + SearchResult search = (SearchResult) importStepHandler.messages.get(5).obj; assertEquals(Collections.singletonList("GC31J2H"), new ArrayList<String>(search.getGeocodes())); cgCache cache = cgeoapplication.getInstance().loadCache("GC31J2H", LoadFlags.LOAD_CACHE_OR_DB); @@ -199,8 +202,8 @@ public class GPXImporterTest extends AbstractResourceInstrumentationTestCase { GPXImporter.ImportGpxZipAttachmentThread importThread = new GPXImporter.ImportGpxZipAttachmentThread(uri, getInstrumentation().getContext().getContentResolver(), listId, importStepHandler, progressHandler); runImportThread(importThread); - assertImportStepMessages(GPXImporter.IMPORT_STEP_START, GPXImporter.IMPORT_STEP_READ_FILE, GPXImporter.IMPORT_STEP_READ_WPT_FILE, GPXImporter.IMPORT_STEP_STORE_CACHES, GPXImporter.IMPORT_STEP_FINISHED); - SearchResult search = (SearchResult) importStepHandler.messages.get(4).obj; + assertImportStepMessages(GPXImporter.IMPORT_STEP_START, GPXImporter.IMPORT_STEP_READ_FILE, GPXImporter.IMPORT_STEP_READ_WPT_FILE, GPXImporter.IMPORT_STEP_STORE_CACHES, GPXImporter.IMPORT_STEP_STORE_STATIC_MAPS, GPXImporter.IMPORT_STEP_FINISHED); + SearchResult search = (SearchResult) importStepHandler.messages.get(5).obj; assertEquals(Collections.singletonList("GC31J2H"), new ArrayList<String>(search.getGeocodes())); cgCache cache = cgeoapplication.getInstance().loadCache("GC31J2H", LoadFlags.LOAD_CACHE_OR_DB); @@ -247,6 +250,11 @@ public class GPXImporterTest extends AbstractResourceInstrumentationTestCase { // workaround to get storage initialized cgeoapplication.getInstance().getAllHistoricCachesCount(); listId = cgeoapplication.getInstance().createList("cgeogpxesTest"); + + importCacheStaticMaps = Settings.isStoreOfflineMaps(); + Settings.setStoreOfflineMaps(true); + importWpStaticMaps = Settings.isStoreOfflineWpMaps(); + Settings.setStoreOfflineWpMaps(true); } @Override @@ -254,6 +262,8 @@ public class GPXImporterTest extends AbstractResourceInstrumentationTestCase { cgeoapplication.getInstance().dropList(listId); cgeoapplication.getInstance().removeList(listId); deleteDirectory(tempDir); + Settings.setStoreOfflineMaps(importCacheStaticMaps); + Settings.setStoreOfflineWpMaps(importWpStaticMaps); super.tearDown(); } |