You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@royale.apache.org by ha...@apache.org on 2020/01/25 21:16:04 UTC

[royale-asjs] 01/01: bead stubs

This is an automated email from the ASF dual-hosted git repository.

harbs pushed a commit to branch feature/router
in repository https://gitbox.apache.org/repos/asf/royale-asjs.git

commit 54b91a216d090c54cfcab93895a4dc9a41908e51
Author: Harbs <ha...@in-tools.com>
AuthorDate: Sat Jan 25 23:15:49 2020 +0200

    bead stubs
---
 .../routing/{RouteState.as => LinkRouting.as}      |  39 +++-
 .../routing/{RouteState.as => ParameterRouting.as} |  28 ++-
 .../apache/royale/routing/RouteNeedsParameters.as  |  64 ++++++
 .../royale/org/apache/royale/routing/RouteState.as |  13 +-
 .../routing/{RouteState.as => RouteToComponent.as} |  20 +-
 .../org/apache/royale/routing/RouteToState.as      | 108 ++++++++++
 .../royale/org/apache/royale/routing/Router.as     | 218 +++++++++------------
 .../routing/{RouteState.as => SetRouteTitle.as}    |  35 +++-
 8 files changed, 363 insertions(+), 162 deletions(-)

diff --git a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/LinkRouting.as
similarity index 54%
copy from frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as
copy to frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/LinkRouting.as
index 84fcc4c..77e5147 100644
--- a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as
+++ b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/LinkRouting.as
@@ -18,17 +18,40 @@
 ////////////////////////////////////////////////////////////////////////////////
 package org.apache.royale.routing
 {
-  public class RouteState
+  import org.apache.royale.core.Bead;
+  import org.apache.royale.core.IStrand;
+  import org.apache.royale.events.Event;
+
+  public class LinkRouting extends Bead
   {
-    public function RouteState(state:String="",title:String="")
+    public function LinkRouting()
     {
-      this.state = state;
-      this.title = title;
+      
+    }
+
+    /**
+     * @royaleignorecoercion org.apache.royale.routing.Router
+     */
+    private function get host():Router{
+      return _strand as Router
+    }
 
+    override public function set strand(value:IStrand):void
+    {
+      _strand = value;
+      COMPILE::JS
+      {
+        document.addEventListener('click', interceptClickEvent);
+      }
+    }
+    /**
+     * If requireHash is true, any link that does not start with hash will be handled by a browser redirect
+     */
+    public var requireHash:Boolean = false;
+    private function interceptClickEvent(ev:Event):void
+    {
+      //TODO find the link target and handle the click event
+      trace(ev);
     }
-    public var state:String;
-    public var title:String;
-    public var parameters:Object;
-    public var path:Array;
   }
 }
\ No newline at end of file
diff --git a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/ParameterRouting.as
similarity index 65%
copy from frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as
copy to frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/ParameterRouting.as
index 84fcc4c..19e1056 100644
--- a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as
+++ b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/ParameterRouting.as
@@ -18,17 +18,29 @@
 ////////////////////////////////////////////////////////////////////////////////
 package org.apache.royale.routing
 {
-  public class RouteState
+  import org.apache.royale.core.Bead;
+  import org.apache.royale.core.IStrand;
+  import org.apache.royale.events.ValueEvent;
+
+  public class ParameterRouting extends Bead
   {
-    public function RouteState(state:String="",title:String="")
+    public function ParameterRouting()
+    {
+      
+    }
+    override public function set strand(value:IStrand):void
+    {
+      _strand = value;
+      listenOnStrand("hashReceived",parseHash)
+      listenOnStrand("hashNeeded",buildHash)
+    }
+    private function buildHash(ev:ValueEvent):void
+    {
+
+    }
+    private function parseHash(ev:ValueEvent):void
     {
-      this.state = state;
-      this.title = title;
 
     }
-    public var state:String;
-    public var title:String;
-    public var parameters:Object;
-    public var path:Array;
   }
 }
\ No newline at end of file
diff --git a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteNeedsParameters.as b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteNeedsParameters.as
new file mode 100644
index 0000000..878d243
--- /dev/null
+++ b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteNeedsParameters.as
@@ -0,0 +1,64 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  Licensed to the Apache Software Foundation (ASF) under one or more
+//  contributor license agreements.  See the NOTICE file distributed with
+//  this work for additional information regarding copyright ownership.
+//  The ASF licenses this file to You 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 org.apache.royale.routing
+{
+  import org.apache.royale.core.Bead;
+
+  public class RouteNeedsParameters extends Bead
+  {
+    public function RouteNeedsParameters()
+    {
+      
+    }
+    // private function buildParameterString():String{
+    //   var retVal:String = "";
+    //   if(_routeState.parameters){
+    //     retVal += "?";
+    //     for(var x:String in _routeState.parameters){
+    //       retVal += x;
+    //       if(_routeState.parameters[x] != undefined){
+    //         retVal += "=" + encodeURIComponent(_routeState.parameters[x]);
+    //         retVal += "&";
+    //       }
+    //     }
+    //     //remove trailing &
+    //     retVal = retVal.slice(0, -1);
+    //   }
+
+    //   return retVal;
+    // }
+
+    // private function parseParameters(query:String):Object
+    // {
+    //   var urlVars:Object;
+    //   if(query){
+    //     var vars:Array = query.split("&");
+    //     if(vars.length){
+    //       urlVars = {};
+    //     }
+    //     for (var i:int=0;i<vars.length;i++) {
+    //         var pair:Array = vars[i].split("=");
+    //         urlVars[pair[0]] = pair[1] == undefined ? undefined : decodeURIComponent(pair[1]);
+    //     }
+    //   }
+    //   return urlVars;
+    // }
+
+  }
+}
\ No newline at end of file
diff --git a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as
index 84fcc4c..cf6190a 100644
--- a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as
+++ b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as
@@ -20,15 +20,14 @@ package org.apache.royale.routing
 {
   public class RouteState
   {
-    public function RouteState(state:String="",title:String="")
+    public function RouteState(path:String="")
     {
-      this.state = state;
-      this.title = title;
-
+      this.path = path;
+      this.parameters = {};
     }
-    public var state:String;
-    public var title:String;
+    public var anchor:String;
     public var parameters:Object;
-    public var path:Array;
+    public var path:String;
+    public var title:String;
   }
 }
\ No newline at end of file
diff --git a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteToComponent.as
similarity index 77%
copy from frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as
copy to frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteToComponent.as
index 84fcc4c..9f18310 100644
--- a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as
+++ b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteToComponent.as
@@ -18,17 +18,19 @@
 ////////////////////////////////////////////////////////////////////////////////
 package org.apache.royale.routing
 {
-  public class RouteState
+  import org.apache.royale.core.Bead;
+  [DefaultProperty("routes")]
+  /**
+   * RouteToComponent is a bead designed for Router
+   * One or more routes should be assigned to the bead
+   */
+  public class RouteToComponent extends Bead
   {
-    public function RouteState(state:String="",title:String="")
+    public function RouteToComponent()
     {
-      this.state = state;
-      this.title = title;
-
+      
     }
-    public var state:String;
-    public var title:String;
-    public var parameters:Object;
-    public var path:Array;
+
+    public var routes:Array;
   }
 }
\ No newline at end of file
diff --git a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteToState.as b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteToState.as
new file mode 100644
index 0000000..4d1df1d
--- /dev/null
+++ b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteToState.as
@@ -0,0 +1,108 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  Licensed to the Apache Software Foundation (ASF) under one or more
+//  contributor license agreements.  See the NOTICE file distributed with
+//  this work for additional information regarding copyright ownership.
+//  The ASF licenses this file to You 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 org.apache.royale.routing
+{
+  import org.apache.royale.core.Bead;
+  import org.apache.royale.core.IStrand;
+  import org.apache.royale.events.ValueEvent;
+  import org.apache.royale.core.IStatesObject;
+  import org.apache.royale.textLayout.debug.assert;
+  import org.apache.royale.utils.callLater;
+
+  public class RouteToState extends Bead
+  {
+    public function RouteToState()
+    {
+      
+    }
+    /**
+     * @royaleignorecoercion org.apache.royale.routing.Router
+     */
+    private function get host():Router{
+      return _strand as Router
+    }
+    
+    override public function set strand(value:IStrand):void
+    {
+      _strand = value;
+      listenOnStrand("hashNeeded",hashNeeded);
+      listenOnStrand("hashReceived",hashReceived);
+      listenOnStrand("stateChange",stateChanged)
+      // attach state change event async to allow adding the parent strand after this is added.
+      callLater(attachStateEvent);
+    }
+    /**
+     * @royaleignorecoercion org.apache.royale.core.IStatesObject
+     */
+    private function attachStateEvent():void
+    {
+      assert(host.host is IStatesObject,"syncState can only be used on IStatesObjects");
+      (host.host as IStatesObject).addEventListener("currentStateChange",handleStateChange);
+
+    }
+    private function handleStateChange():void
+    {
+      if(settingState)// don't do anything if the event was fired as a result of a hash.
+        return;
+      //TODO what about a parent path
+      host.routeState.path = (host.host as IStatesObject).currentState;
+      host.setState();
+    }
+    private function hashNeeded(ev:ValueEvent):void
+    {
+      var hash:String = ev.value;
+      // don't overwrite path, parameters and anchor
+      var parentPath:String = ""
+      if(hash.indexOf("/") != -1)
+      {
+        parentPath = hash.slice(0,hash.lastIndexOf("/")+1);
+      }
+      var trailing:String = "";
+      // if we have parameters, we don't care if we also have an anchor
+      var delim:String = ""
+      if(hash.indexOf("?") != -1)
+        delim = "?"
+
+      else if(hash.indexOf("#") != -1)
+        delim = "#"
+      if(delim)
+        trailing = hash.slice(hash.indexOf(delim));
+      
+      ev.value = parentPath + (host.host as IStatesObject).currentState + trailing;
+    }
+    private function hashReceived(ev:ValueEvent):void
+    {
+      var hash
+    }
+    private var settingState:Boolean;
+    /**
+     * @royaleignorecoercion org.apache.royale.core.IStatesObject
+     */
+    private function stateChanged(ev:ValueEvent):void
+    {
+        assert(host.host is IStatesObject,"syncState can only be used on IStatesObjects");
+        settingState = true;
+        //TODO what about using the base name of the path?
+        (host.host as IStatesObject).currentState = host.routeState.path;
+        settingState = false;
+
+    }
+
+  }
+}
\ No newline at end of file
diff --git a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/Router.as b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/Router.as
index 98b9fc6..b819215 100644
--- a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/Router.as
+++ b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/Router.as
@@ -24,6 +24,14 @@ package org.apache.royale.routing
   import org.apache.royale.core.IStatesObject;
   import org.apache.royale.events.Event;
   import org.apache.royale.core.IInitialViewApplication;
+  import org.apache.royale.core.Strand;
+  import org.apache.royale.core.IBead;
+  import org.apache.royale.events.IEventDispatcher;
+  import org.apache.royale.events.ValueEvent;
+  import org.apache.royale.core.IUIBase;
+  import org.apache.royale.core.IMXMLDocument;
+  import org.apache.royale.utils.MXMLDataInterpreter;
+  [DefaultProperty("beads")]
     /**
      *  Dispatched when the state is changed.
      *
@@ -44,7 +52,7 @@ package org.apache.royale.routing
      *  @playerversion AIR 2.6
      *  @productversion Royale 0.9.7
      */
-  public class Router extends DispatcherBead
+  public class Router extends Strand implements IBead, IMXMLDocument
   {
     public function Router()
     {
@@ -60,14 +68,14 @@ package org.apache.royale.routing
      *  @playerversion AIR 2.6
      *  @productversion Royale 0.9.7
      */
-    public var syncState:Boolean;
-		override public function set strand(value:IStrand):void
+    public var host:IStrand;
+    private var _strand:IStrand;
+		public function set strand(value:IStrand):void
 		{	
 			_strand = value;
 			COMPILE::JS
 			{
 				window.addEventListener("hashchange", hashChangeHandler);
-        initialTitle = document.title;
 			}
       // If it's an Application, listen to applicationComplete
       if(_strand is IInitialViewApplication)
@@ -76,6 +84,14 @@ package org.apache.royale.routing
       else
         listenOnStrand("initComplete",onInit);
 		}
+    /**
+     * Helper function to attach event listener without the need for casting
+     * @royaleignorecoercion org.apache.royale.events.IEventDispatcher
+     */
+    protected function listenOnStrand(eventType:String,handler:Function,capture:Boolean=false):void
+    {
+      (_strand as IEventDispatcher).addEventListener(eventType, handler, capture);
+    }
     private function onInit(event:Event):void
     {
       COMPILE::JS
@@ -86,15 +102,9 @@ package org.apache.royale.routing
         }
       }
     }
-    private var initialTitle:String;
 		private function hashChangeHandler():void
 		{
       parseHash();
-      if(syncState)
-      {
-        assert(_strand is IStatesObject,"syncState can only be used on IStatesObjects");
-        (_strand as IStatesObject).currentState = _routeState.state;
-      }
 			dispatchEvent(new Event("stateChange"));
 		}
     private function parseHash():void
@@ -108,60 +118,33 @@ package org.apache.royale.routing
           index = 1;
         }
         hash = hash.slice(index+1);
-        var paths:Array = hash.split("/");
-        var statePart:String = paths.pop();
-        var splitParts:Array = statePart.split("?");
-        statePart = splitParts[0];
-        _routeState = new RouteState(statePart,document.title);
-        _routeState.path = paths;
-        _routeState.parameters = parseParameters(splitParts[1]);
+        var ev:ValueEvent = new ValueEvent("hashReceived",hash);
+        dispatchEvent(ev);
+        // var splitParts:Array = hash.split("?");
+        // var path:String = 
+        // var paths:Array = hash.split("/");
+        // var statePart:String = paths.pop();
+        // var splitParts:Array = statePart.split("?");
+        // statePart = splitParts[0];
+        // _routeState = new RouteState(statePart,document.title);
+        // _routeState.path = paths;
+        // _routeState.parameters = parseParameters(splitParts[1]);
       }
     }
-    private function parseParameters(query:String):Object
-    {
-      var urlVars:Object;
-      if(query){
-        var vars:Array = query.split("&");
-        if(vars.length){
-          urlVars = {};
-        }
-        for (var i:int=0;i<vars.length;i++) {
-            var pair:Array = vars[i].split("=");
-            urlVars[pair[0]] = pair[1] == undefined ? undefined : decodeURIComponent(pair[1]);
-        }
-      }
-      return urlVars;
-    }
 
-    private function buildHash():String
-    {
-      var hash:String = "#!";
-      if(_routeState.path && routeState.path.length){
-        hash += (_routeState.path.join("/") + "/");
-      }
-      if(_routeState.state){
-        hash += _routeState.state;
-      }
-      hash+= buildParameterString();
-      return hash;
-    }
-    private function buildParameterString():String{
-      var retVal:String = "";
-      if(_routeState.parameters){
-        retVal += "?";
-        for(var x:String in _routeState.parameters){
-          retVal += x;
-          if(_routeState.parameters[x] != undefined){
-            retVal += "=" + encodeURIComponent(_routeState.parameters[x]);
-            retVal += "&";
-          }
-        }
-        //remove trailing &
-        retVal = retVal.slice(0, -1);
-      }
+    // private function buildHash():String
+    // {
 
-      return retVal;
-    }
+    //   var hash:String = "#!";
+    //   if(_routeState.path && routeState.path.length){
+    //     hash += (_routeState.path.join("/") + "/");
+    //   }
+    //   if(_routeState.state){
+    //     hash += _routeState.state;
+    //   }
+    //   hash+= buildParameterString();
+    //   return hash;
+    // }
 
     private var _routeState:RouteState;
 
@@ -188,7 +171,16 @@ package org.apache.royale.routing
     {
       COMPILE::JS
       {
-        window.history.pushState({"title":_routeState.title},_routeState.title,buildHash());
+        var hash:String = "#!";
+        var ev:ValueEvent = new ValueEvent("hashNeeded","");
+        dispatchEvent(ev);
+        var stateEv:ValueEvent = new ValueEvent("stateNeeded",{});
+        dispatchEvent(stateEv);
+        if(!ev.defaultPrevented)
+        {
+          hash += ev.value;
+          window.history.pushState(stateEv.value,_routeState.title,hash);
+        }
         if(_routeState.title)
         {
           document.title = _routeState.title;
@@ -205,70 +197,52 @@ package org.apache.royale.routing
     public function renderState():void
     {
       setState();
-      if(syncState)
-      {
-        assert(_strand is IStatesObject,"syncState can only be used on IStatesObjects");
-        (_strand as IStatesObject).currentState = _routeState.state;
-      }
+      // if(syncState)
+      // {
+      //   assert(_strand is IStatesObject,"syncState can only be used on IStatesObjects");
+      //   (_strand as IStatesObject).currentState = _routeState.state;
+      // }
       dispatchEvent(new Event("stateChange"));
     }
-    private function setTitle():void
-    {
-      COMPILE::JS
-      {
-        if(window.history.state){
-          document.title = window.history.state["title"];
-        } else {
-          document.title = initialTitle;
-        }
-      }
-    }
-    /**
-     * Goes forward in the history
-     *  @langversion 3.0
-     *  @playerversion Flash 10.2
-     *  @playerversion AIR 2.6
-     *  @productversion Royale 0.9.7
-     */
-    public function forward():void{
-      COMPILE::JS
-      {
-         window.history.forward();
-         setTitle();
-         parseHash();
-      }
-    }
-    /**
-     * Goes backwards in the history
-     *  @langversion 3.0
-     *  @playerversion Flash 10.2
-     *  @playerversion AIR 2.6
-     *  @productversion Royale 0.9.7
-     */
-    public function back():void{
-      COMPILE::JS
-      {
-         window.history.back();
-         setTitle();
-         parseHash();
-      }
-    }
 
-    /**
-     * Moved the specified number of steps (forward or backwards) in the history
-     * calling it with 0 or no value will reload the page.
-     *  @langversion 3.0
-     *  @playerversion Flash 10.2
-     *  @playerversion AIR 2.6
-     *  @productversion Royale 0.9.7
-     */
-    public function go(steps:int=0):void{
-      COMPILE::JS
-      {
-         window.history.go(steps);
-         parseHash();
-      }
-    }
+		private var _mxmlDescriptor:Array;
+		private var _mxmlDocument:Object = this;
+
+		/**
+		 *  @copy org.apache.royale.core.Application#MXMLDescriptor
+		 *  
+		 *  @langversion 3.0
+		 *  @playerversion Flash 10.2
+		 *  @playerversion AIR 2.6
+		 *  @productversion Royale 0.8
+		 */
+		public function get MXMLDescriptor():Array
+		{
+			return _mxmlDescriptor;
+		}
+		
+		/**
+		 *  @private
+		 */
+		public function setMXMLDescriptor(document:Object, value:Array):void
+		{
+			_mxmlDocument = document;
+			_mxmlDescriptor = value;
+		}
+		
+		/**
+		 *  @copy org.apache.royale.core.Application#generateMXMLAttributes()
+		 *  
+		 *  @langversion 3.0
+		 *  @playerversion Flash 10.2
+		 *  @playerversion AIR 2.6
+		 *  @productversion Royale 0.8
+		 */
+		public function generateMXMLAttributes(data:Array):void
+		{
+			MXMLDataInterpreter.generateMXMLProperties(this, data);
+		}
+		
 
   }
 }
\ No newline at end of file
diff --git a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/SetRouteTitle.as
similarity index 63%
copy from frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as
copy to frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/SetRouteTitle.as
index 84fcc4c..4b86e42 100644
--- a/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/RouteState.as
+++ b/frameworks/projects/Basic/src/main/royale/org/apache/royale/routing/SetRouteTitle.as
@@ -18,17 +18,36 @@
 ////////////////////////////////////////////////////////////////////////////////
 package org.apache.royale.routing
 {
-  public class RouteState
+  import org.apache.royale.core.Bead;
+  import org.apache.royale.core.IStrand;
+
+  public class SetRouteTitle extends Bead
   {
-    public function RouteState(state:String="",title:String="")
+    public function SetRouteTitle()
     {
-      this.state = state;
-      this.title = title;
+      
+    }
 
+    override public function set strand(value:IStrand):void
+    {
+      COMPILE::JS
+      {
+        initialTitle = document.title;
+      }
     }
-    public var state:String;
-    public var title:String;
-    public var parameters:Object;
-    public var path:Array;
+
+    private var initialTitle:String;
+    private function setTitle():void
+    {
+      COMPILE::JS
+      {
+        if(window.history.state){
+          document.title = window.history.state["title"];
+        } else {
+          document.title = initialTitle;
+        }
+      }
+    }
+
   }
 }
\ No newline at end of file