diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java b/Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java index ff9c0ea8..8496a3b6 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java @@ -16,6 +16,13 @@ package com.arialyy.aria.core.common; import com.arialyy.annotations.TaskEnum; +import com.arialyy.aria.core.download.DownloadGroupTaskListener; +import com.arialyy.aria.core.download.DownloadTaskListener; +import com.arialyy.aria.core.scheduler.DownloadTaskInternalListenerInterface; +import com.arialyy.aria.core.scheduler.M3U8PeerTaskListenerInterface; +import com.arialyy.aria.core.scheduler.SubTaskListenerInterface; +import com.arialyy.aria.core.upload.UploadTaskListener; + import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -69,12 +76,25 @@ public class ProxyHelper { * @return {@link #PROXY_TYPE_DOWNLOAD},如果没有实体对象则返回空的list */ public Set checkProxyType(Class clazz) { - final String className = clazz.getName(); Set result = mProxyCache.get(clazz.getName()); if (result != null) { return result; } - result = new HashSet<>(); + result = checkProxyTypeByInterface(clazz); + if (result != null) { + return result; + } + result = checkProxyTypeByProxyClass(clazz); + + if (!result.isEmpty()) { + mProxyCache.put(clazz.getName(), result); + } + return result; + } + + private Set checkProxyTypeByProxyClass(Class clazz) { + final String className = clazz.getName(); + Set result = new HashSet<>(); if (checkProxyExist(className, TaskEnum.DOWNLOAD_GROUP.proxySuffix)) { result.add(PROXY_TYPE_DOWNLOAD_GROUP); } @@ -93,9 +113,31 @@ public class ProxyHelper { if (checkProxyExist(className, TaskEnum.DOWNLOAD_GROUP_SUB.proxySuffix)) { result.add(PROXY_TYPE_DOWNLOAD_GROUP_SUB); } + return result; + } + + private Set checkProxyTypeByInterface(Class clazz) { + if (!DownloadTaskInternalListenerInterface.class.isAssignableFrom(clazz)) { + return null; + } + Set result = new HashSet<>(); + if (DownloadGroupTaskListener.class.isAssignableFrom(clazz)) { + result.add(PROXY_TYPE_DOWNLOAD_GROUP); + } + if (DownloadTaskListener.class.isAssignableFrom(clazz)) { + result.add(PROXY_TYPE_DOWNLOAD); + } - if (!result.isEmpty()) { - mProxyCache.put(clazz.getName(), result); + if (UploadTaskListener.class.isAssignableFrom(clazz)) { + result.add(PROXY_TYPE_UPLOAD); + } + + if (M3U8PeerTaskListenerInterface.class.isAssignableFrom(clazz)) { + result.add(PROXY_TYPE_M3U8_PEER); + } + + if (SubTaskListenerInterface.class.isAssignableFrom(clazz)) { + result.add(PROXY_TYPE_DOWNLOAD_GROUP_SUB); } return result; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTaskListener.java new file mode 100644 index 00000000..db90ffed --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTaskListener.java @@ -0,0 +1,11 @@ +package com.arialyy.aria.core.download; + +import com.arialyy.aria.core.scheduler.NormalTaskListenerInterface; +import com.arialyy.aria.core.task.DownloadGroupTask; + +/** + * @author ChenFei(chenfei0928 @ gmail.com) + * @date 2020-07-07 14:12 + */ +public interface DownloadGroupTaskListener extends NormalTaskListenerInterface { +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTaskListener.java new file mode 100644 index 00000000..04ff9171 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTaskListener.java @@ -0,0 +1,11 @@ +package com.arialyy.aria.core.download; + +import com.arialyy.aria.core.scheduler.NormalTaskListenerInterface; +import com.arialyy.aria.core.task.DownloadTask; + +/** + * @author ChenFei(chenfei0928 @ gmail.com) + * @date 2020-07-07 14:12 + */ +public interface DownloadTaskListener extends NormalTaskListenerInterface { +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/DownloadTaskInternalListenerInterface.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/DownloadTaskInternalListenerInterface.java new file mode 100644 index 00000000..993b54f1 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/DownloadTaskInternalListenerInterface.java @@ -0,0 +1,10 @@ +package com.arialyy.aria.core.scheduler; + +/** + * 直接实现监听器回调接口的基类,不对外部直接开放,仅作为内部监听器的父接口使用 + * + * @author ChenFei(chenfei0928 @ gmail.com) + * @date 2020-07-07 15:18 + */ +public interface DownloadTaskInternalListenerInterface { +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/M3U8PeerTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/M3U8PeerTaskListener.java index 3df14066..1824c065 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/M3U8PeerTaskListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/M3U8PeerTaskListener.java @@ -19,15 +19,15 @@ package com.arialyy.aria.core.scheduler; * Created by Aria.Lao on 2019/6/26. * m3u8切片事件回调类 */ -public class M3U8PeerTaskListener implements ISchedulerListener { +public class M3U8PeerTaskListener implements M3U8PeerTaskListenerInterface, ISchedulerListener{ - public void onPeerStart(final String m3u8Url, final String peerPath, final int peerIndex) { + @Override public void onPeerStart(final String m3u8Url, final String peerPath, final int peerIndex) { } - public void onPeerComplete(final String m3u8Url, final String peerPath, final int peerIndex) { + @Override public void onPeerComplete(final String m3u8Url, final String peerPath, final int peerIndex) { } - public void onPeerFail(final String m3u8Url, final String peerPath, final int peerIndex) { + @Override public void onPeerFail(final String m3u8Url, final String peerPath, final int peerIndex) { } @Override public void setListener(Object obj) { diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/M3U8PeerTaskListenerInterface.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/M3U8PeerTaskListenerInterface.java new file mode 100644 index 00000000..5cc8837c --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/M3U8PeerTaskListenerInterface.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.scheduler; + +/** + * Created by Aria.Lao on 2019/6/26. + * m3u8切片事件回调类 + */ +public interface M3U8PeerTaskListenerInterface extends DownloadTaskInternalListenerInterface { + + public void onPeerStart(final String m3u8Url, final String peerPath, final int peerIndex); + + public void onPeerComplete(final String m3u8Url, final String peerPath, final int peerIndex); + + public void onPeerFail(final String m3u8Url, final String peerPath, final int peerIndex); +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListener.java index 4757ab9a..82153203 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListener.java @@ -24,12 +24,12 @@ import com.arialyy.aria.core.task.UploadTask; * Created by Aria.Lao on 2017/6/7. * 普通任务事件{@link DownloadTask}、{@link UploadTask}、{@link DownloadGroupTask}回调类 */ -public class NormalTaskListener implements ISchedulerListener { +public class NormalTaskListener implements NormalTaskListenerInterface, ISchedulerListener { /** * 队列已经满了,继续创建任务,将会回调该方法 */ - public void onWait(TASK task) { + @Override public void onWait(TASK task) { } @@ -37,42 +37,42 @@ public class NormalTaskListener implements ISchedulerListene * 预处理,有时有些地址链接比较慢,这时可以先在这个地方出来一些界面上的UI,如按钮的状态。 * 在这个回调中,任务是获取不到文件大小,下载速度等参数 */ - public void onPre(TASK task) { + @Override public void onPre(TASK task) { } /** * 任务预加载完成 */ - public void onTaskPre(TASK task) { + @Override public void onTaskPre(TASK task) { } /** * 任务恢复下载 */ - public void onTaskResume(TASK task) { + @Override public void onTaskResume(TASK task) { } /** * 任务开始 */ - public void onTaskStart(TASK task) { + @Override public void onTaskStart(TASK task) { } /** * 任务停止 */ - public void onTaskStop(TASK task) { + @Override public void onTaskStop(TASK task) { } /** * 任务取消 */ - public void onTaskCancel(TASK task) { + @Override public void onTaskCancel(TASK task) { } @@ -88,25 +88,25 @@ public class NormalTaskListener implements ISchedulerListene /** * 任务失败 */ - public void onTaskFail(TASK task, Exception e) { + @Override public void onTaskFail(TASK task, Exception e) { } /** * 任务完成 */ - public void onTaskComplete(TASK task) { + @Override public void onTaskComplete(TASK task) { } /** * 任务执行中 */ - public void onTaskRunning(TASK task) { + @Override public void onTaskRunning(TASK task) { } - public void onNoSupportBreakPoint(TASK task) { + @Override public void onNoSupportBreakPoint(TASK task) { } diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListenerInterface.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListenerInterface.java new file mode 100644 index 00000000..4d9d4ad1 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListenerInterface.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.scheduler; + +import com.arialyy.aria.core.task.DownloadGroupTask; +import com.arialyy.aria.core.task.DownloadTask; +import com.arialyy.aria.core.task.ITask; +import com.arialyy.aria.core.task.UploadTask; + +/** + * Created by Aria.Lao on 2017/6/7. + * 普通任务事件{@link DownloadTask}、{@link UploadTask}、{@link DownloadGroupTask}回调类 + */ +public interface NormalTaskListenerInterface extends DownloadTaskInternalListenerInterface { + + /** + * 队列已经满了,继续创建任务,将会回调该方法 + */ + public void onWait(TASK task); + + /** + * 预处理,有时有些地址链接比较慢,这时可以先在这个地方出来一些界面上的UI,如按钮的状态。 + * 在这个回调中,任务是获取不到文件大小,下载速度等参数 + */ + public void onPre(TASK task); + + /** + * 任务预加载完成 + */ + public void onTaskPre(TASK task); + + /** + * 任务恢复下载 + */ + public void onTaskResume(TASK task); + + /** + * 任务开始 + */ + public void onTaskStart(TASK task); + + /** + * 任务停止 + */ + public void onTaskStop(TASK task); + + /** + * 任务取消 + */ + public void onTaskCancel(TASK task); + + /** + * 任务失败 + */ + public void onTaskFail(TASK task, Exception e); + + /** + * 任务完成 + */ + public void onTaskComplete(TASK task); + + /** + * 任务执行中 + */ + public void onTaskRunning(TASK task); + + public void onNoSupportBreakPoint(TASK task); +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/SubTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/SubTaskListener.java index 036b1185..3868e9f6 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/SubTaskListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/SubTaskListener.java @@ -23,29 +23,29 @@ import com.arialyy.aria.core.task.ITask; * 子任务事件回调类 */ public class SubTaskListener - implements ISchedulerListener { + implements SubTaskListenerInterface, ISchedulerListener { - public void onNoSupportBreakPoint(TASK task) { + @Override public void onNoSupportBreakPoint(TASK task) { } - public void onSubTaskPre(TASK task, SUB_ENTITY subTask) { + @Override public void onSubTaskPre(TASK task, SUB_ENTITY subTask) { } - public void onSubTaskStart(TASK task, SUB_ENTITY subTask) { + @Override public void onSubTaskStart(TASK task, SUB_ENTITY subTask) { } - public void onSubTaskStop(TASK task, SUB_ENTITY subTask) { + @Override public void onSubTaskStop(TASK task, SUB_ENTITY subTask) { } - public void onSubTaskCancel(TASK task, SUB_ENTITY subTask) { + @Override public void onSubTaskCancel(TASK task, SUB_ENTITY subTask) { } - public void onSubTaskComplete(TASK task, SUB_ENTITY subTask) { + @Override public void onSubTaskComplete(TASK task, SUB_ENTITY subTask) { } @@ -54,11 +54,11 @@ public class SubTaskListener extends DownloadTaskInternalListenerInterface { + + public void onNoSupportBreakPoint(TASK task); + + public void onSubTaskPre(TASK task, SUB_ENTITY subTask); + + public void onSubTaskStart(TASK task, SUB_ENTITY subTask); + + public void onSubTaskStop(TASK task, SUB_ENTITY subTask); + + public void onSubTaskCancel(TASK task, SUB_ENTITY subTask); + + public void onSubTaskComplete(TASK task, SUB_ENTITY subTask); + + public void onSubTaskFail(TASK task, SUB_ENTITY subTask, Exception e); + + public void onSubTaskRunning(TASK task, SUB_ENTITY subTask); +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/TaskSchedulers.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/TaskSchedulers.java index adc4445a..b54cabdf 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/TaskSchedulers.java +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/TaskSchedulers.java @@ -18,7 +18,6 @@ package com.arialyy.aria.core.scheduler; import android.content.Intent; import android.os.Bundle; import android.os.Message; -import android.util.Log; import com.arialyy.annotations.TaskEnum; import com.arialyy.aria.core.AriaConfig; import com.arialyy.aria.core.common.AbsEntity; @@ -54,7 +53,7 @@ public class TaskSchedulers implements ISchedulers { private static volatile TaskSchedulers INSTANCE; private static FailureTaskHandler mFailureTaskHandler; - private Map> mObservers = new ConcurrentHashMap<>(); + private Map> mObservers = new ConcurrentHashMap<>(); private AriaConfig mAriaConfig; private TaskSchedulers() { @@ -99,15 +98,19 @@ public class TaskSchedulers implements ISchedulers { */ public void register(Object obj, TaskEnum taskEnum) { String targetName = obj.getClass().getName(); - Map listeners = mObservers.get(getKey(obj)); + Map listeners = mObservers.get(getKey(obj)); if (listeners == null) { listeners = new ConcurrentHashMap<>(); mObservers.put(getKey(obj), listeners); } - String proxyClassName = targetName + taskEnum.proxySuffix; if (!hasProxyListener(listeners, taskEnum)) { + if (obj instanceof DownloadTaskInternalListenerInterface) { + listeners.put(taskEnum, obj); + return; + } + String proxyClassName = targetName + taskEnum.proxySuffix; ISchedulerListener listener = createListener(proxyClassName); if (listener != null) { listener.setListener(obj); @@ -124,7 +127,7 @@ public class TaskSchedulers implements ISchedulers { * @param taskEnum 代理类类型 * @return true,已注册代理类,false,没有注册代理类 */ - private boolean hasProxyListener(Map listeners, TaskEnum taskEnum) { + private boolean hasProxyListener(Map listeners, TaskEnum taskEnum) { return !listeners.isEmpty() && listeners.get(taskEnum) != null; } @@ -137,9 +140,9 @@ public class TaskSchedulers implements ISchedulers { if (!mObservers.containsKey(getKey(obj))) { return; } - for (Iterator>> iter = + for (Iterator>> iter = mObservers.entrySet().iterator(); iter.hasNext(); ) { - Map.Entry> entry = iter.next(); + Map.Entry> entry = iter.next(); if (entry.getKey().equals(getKey(obj))) { iter.remove(); @@ -202,12 +205,12 @@ public class TaskSchedulers implements ISchedulers { if (mObservers.size() > 0) { Set keys = mObservers.keySet(); for (String key : keys) { - Map listeners = mObservers.get(key); + Map listeners = mObservers.get(key); if (listeners == null || listeners.isEmpty()) { continue; } - M3U8PeerTaskListener listener = - (M3U8PeerTaskListener) listeners.get(TaskEnum.M3U8_PEER); + M3U8PeerTaskListenerInterface listener = + (M3U8PeerTaskListenerInterface) listeners.get(TaskEnum.M3U8_PEER); if (listener == null) { continue; } @@ -235,6 +238,7 @@ public class TaskSchedulers implements ISchedulers { boolean canSend = mAriaConfig.getAConfig().isUseBroadcast(); if (canSend) { Intent intent = new Intent(ISchedulers.ARIA_TASK_INFO_ACTION); + intent.setPackage(mAriaConfig.getAPP().getPackageName()); intent.putExtras(data); mAriaConfig.getAPP().sendBroadcast(intent); } @@ -250,12 +254,12 @@ public class TaskSchedulers implements ISchedulers { if (mObservers.size() > 0) { Set keys = mObservers.keySet(); for (String key : keys) { - Map listeners = mObservers.get(key); + Map listeners = mObservers.get(key); if (listeners == null || listeners.isEmpty()) { continue; } - SubTaskListener listener = - (SubTaskListener) listeners.get(TaskEnum.DOWNLOAD_GROUP_SUB); + SubTaskListenerInterface listener = + (SubTaskListenerInterface) listeners.get(TaskEnum.DOWNLOAD_GROUP_SUB); if (listener == null) { continue; } @@ -358,6 +362,7 @@ public class TaskSchedulers implements ISchedulers { boolean canSend = mAriaConfig.getAConfig().isUseBroadcast(); if (canSend) { Intent intent = new Intent(ISchedulers.ARIA_TASK_INFO_ACTION); + intent.setPackage(mAriaConfig.getAPP().getPackageName()); Bundle b = new Bundle(); b.putInt(ISchedulers.TASK_TYPE, taskType); b.putInt(ISchedulers.TASK_STATE, ISchedulers.FAIL); @@ -368,18 +373,18 @@ public class TaskSchedulers implements ISchedulers { if (mObservers.size() > 0) { Set keys = mObservers.keySet(); for (String key : keys) { - Map listeners = mObservers.get(key); + Map listeners = mObservers.get(key); if (listeners == null || listeners.isEmpty()) { continue; } - NormalTaskListener listener = null; + NormalTaskListenerInterface listener = null; if (mObservers.get(key) != null) { if (taskType == ITask.DOWNLOAD) { - listener = (NormalTaskListener) listeners.get(TaskEnum.DOWNLOAD); + listener = (NormalTaskListenerInterface) listeners.get(TaskEnum.DOWNLOAD); } else if (taskType == ITask.DOWNLOAD_GROUP) { - listener = (NormalTaskListener) listeners.get(TaskEnum.DOWNLOAD_GROUP); + listener = (NormalTaskListenerInterface) listeners.get(TaskEnum.DOWNLOAD_GROUP); } else if (taskType == ITask.DOWNLOAD_GROUP) { - listener = (NormalTaskListener) listeners.get(TaskEnum.UPLOAD); + listener = (NormalTaskListenerInterface) listeners.get(TaskEnum.UPLOAD); } } if (listener != null) { @@ -399,18 +404,18 @@ public class TaskSchedulers implements ISchedulers { if (mObservers.size() > 0) { Set keys = mObservers.keySet(); for (String key : keys) { - Map listeners = mObservers.get(key); + Map listeners = mObservers.get(key); if (listeners == null || listeners.isEmpty()) { continue; } - NormalTaskListener listener = null; + NormalTaskListenerInterface listener = null; if (mObservers.get(key) != null) { if (task instanceof DownloadTask) { - listener = (NormalTaskListener) listeners.get(TaskEnum.DOWNLOAD); + listener = (NormalTaskListenerInterface) listeners.get(TaskEnum.DOWNLOAD); } else if (task instanceof DownloadGroupTask) { - listener = (NormalTaskListener) listeners.get(TaskEnum.DOWNLOAD_GROUP); + listener = (NormalTaskListenerInterface) listeners.get(TaskEnum.DOWNLOAD_GROUP); } else if (task instanceof UploadTask) { - listener = (NormalTaskListener) listeners.get(TaskEnum.UPLOAD); + listener = (NormalTaskListenerInterface) listeners.get(TaskEnum.UPLOAD); } } if (listener != null) { @@ -420,7 +425,7 @@ public class TaskSchedulers implements ISchedulers { } } - private void normalTaskCallback(int state, TASK task, NormalTaskListener listener) { + private void normalTaskCallback(int state, TASK task, NormalTaskListenerInterface listener) { if (listener != null) { if (task == null && state != ISchedulers.CHECK_FAIL) { ALog.e(TAG, "TASK 为null,回调失败"); @@ -497,6 +502,7 @@ public class TaskSchedulers implements ISchedulers { */ private Intent createData(int taskState, int taskType, AbsEntity entity) { Intent intent = new Intent(ISchedulers.ARIA_TASK_INFO_ACTION); + intent.setPackage(mAriaConfig.getAPP().getPackageName()); Bundle b = new Bundle(); b.putInt(ISchedulers.TASK_TYPE, taskType); b.putInt(ISchedulers.TASK_STATE, taskState); diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTaskListener.java new file mode 100644 index 00000000..359efb6c --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTaskListener.java @@ -0,0 +1,13 @@ +package com.arialyy.aria.core.upload; + +import com.arialyy.aria.core.scheduler.NormalTaskListenerInterface; +import com.arialyy.aria.core.task.UploadTask; + +/** + * 上传任务接口 + * + * @author ChenFei(chenfei0928 @ gmail.com) + * @date 2020-07-07 13:23 + */ +public interface UploadTaskListener extends NormalTaskListenerInterface { +}